Disclaimer: The purpose of the Open Case Studies project is to demonstrate the use of various data science methods, tools, and software in the context of messy, real-world data. A given case study does not cover all aspects of the research process, is not claiming to be the most appropriate way to analyze a given data set, and should not be used in the context of making policy decisions without external consultation from scientific experts.

This work is licensed under the Creative Commons Attribution-NonCommercial 3.0 (CC BY-NC 3.0) United States License.

Motivation


A variety of different sources contribute different types of pollutants to what we call air pollution.

Some sources are natural while others are anthropogenic (human derived):

Major types of air pollutants

  1. Gaseous - Carbon Monoxide (CO), Ozone (O3), Nitrogen Oxides(NO, NO2), Sulpher Dioxide (SO2)
  2. Particulate - small liquids and solids suspended in the air (includes lead- can include certain types of dust)
  3. Dust - small solids (larger than particulates) that can be suspended in the air for some time but eventually settle
  4. Biological - pollen, bacteria, viruses, mold spores

See here for more detail on the types of pollutants in the air.

Particulate pollution

Air pollution particulates are generally described by their size.

There are 3 major categories:

  1. Large Coarse Particulate Mater - has diameter of >10 micrometers (10 µm)

  2. Coarse Particulate Mater (called PM10-2.5) - has diameter of between 2.5 µm and 10 µm

  3. Fine Particulate Mater (called PM2.5) - has diameter of < 2.5 µm

PM10 includes any particulate mater <10 µm (both coarse and fine particulate mater)

Here you can see how these sizes compare with a human hair:

[source]

The following plot and table show the relative sizes of these different pollutants in micrometers (µm):

[source]

This table shows how deeply some of the smaller fine particles can penetrate within the human body:

Negative impact of particulate exposure on health

Exposure to air pollution is associated with higher rates of mortality in older adults and is known to be a risk factor for many diseases and conditions including but not limited to:

  1. Asthma - fine particle exposure (PM2.5) was found to be associated with higher rates of asthma in children
  2. Inflammation in type 1 diabetes - fine particle exposure (PM2.5) from traffic-related air pollution was associated with increased measures of inflammatory markers in youths with Type 1 diabetes
  3. Lung function and emphysema - higher concentrations of ozone (O3), nitrogen oxides (NOx), black carbon, and fine particle exposure PM2.5 , at study baseline were significantly associated with greater increases in percent emphysema per 10 years
  4. Low birthweight - fine particle exposure(PM2.5) was associated with lower birth weight in full-term live births
  5. Viral Infection - higher rates of infection and increased severity of infection are associated with higher exposures to pollution levels including fine particle exposure (PM2.5)

See this review article for more information about sources of air pollution and the influence of air pollution on health.

Sparse monitoring is problematic for Public Health

Historically, epidemiological studies would assess the influence of air pollution on health outcomes by relying on a number of monitors located around the country. However, as can be seen in the following figure, these monitors are relatively sparse in certain regions of the country. Furthermore, dramatic differences in pollution rates can be seen even within the same city.

[source]

This lack of granularity in air pollution monitoring has hindered our ability to discern the full impact of air pollution on health and to identify at-risk locations.

Machine learning offers a solution

An article published in the Environmental Health journal dealt with this issue by using data, including population density, road density, among other features, to model or predict air pollution levels at a more localized scale using machine learning (ML) methods.

Yanosky, J. D. et al. Spatio-temporal modeling of particulate air pollution in the conterminous United States using geographic and meteorological predictors. Environ Health 13, 63 (2014).

The authors of this article state that:

“Exposure to atmospheric particulate matter (PM) remains an important public health concern, although it remains difficult to quantify accurately across large geographic areas with sufficiently high spatial resolution. Recent epidemiologic analyses have demonstrated the importance of spatially- and temporally-resolved exposure estimates, which show larger PM-mediated health effects as compared to nearest monitor or county-specific ambient concentrations.”

The article above demonstrates that machine learning methods can be used to predict air pollution levels when traditional monitoring systems are not available in a particular area or when there is not enough spatial granularity with current monitoring systems. We will use similar methods to predict annual air pollution levels spatially within the US.

Main Question


Our main question:

  1. Can we predict annual average air pollution concentrations at the granularity of zip code regional levels using predictors such as data about population density, urbanization, road density, as well as, satellite pollution data and chemical modeling data?

Learning Objectives


In this case study, we will walk you through importing data from CSV files and performing machine learning methods to predict our outcome variable of interest (in this case annual fine particle air pollution estimates).

We will especially focus on using packages and functions from the tidyverse, and more specifically the tidymodels package/ecosystem primarily developed and maintained by Max Kuhn and Davis Vaughan. This package loads more modeling related packages like rsample, recipes, parsnip, yardstick, workflows, and tune packages.

The tidyverse is a library of packages created by RStudio. While some students may be familiar with previous R programming packages, these packages make data science in R especially efficient.

The skills, methods, and concepts that students will be familiar with by the end of this case study are:

Data science skills:

  1. Familiarity with the tidymodels ecosystem
  2. Ability to evaluate correlation among predictor variables (corrplot and GGally)
  3. Ability to implement tidymodels packages such as rsample to split the data into training and testing sets as well as cross validation sets.
  4. Ability to use the recipes, parsnip, and workflows to train and test a linear regression model and random forest model
  5. Demonstrate how to visualize geo-spatial data using ggplot2

Statistical concepts and methods:

  1. Basic understanding the utility of machine learning for prediction and classification
  2. Understanding of the need for training and test set
  3. Understanding of the utility of cross validation
  4. Understanding of random forest
  5. How to interpret root mean squared error (rmse) to assess performance for prediction

We will begin by loading the packages that we will need:

Package Use
here to easily load and save data
readr to import CSV files
dplyr to view/arrange/filter/select/compare specific subsets of data
skimr to get an overview of data
summarytools to get an overview of data in a different style
magrittr to use the %<>% pipping operator
corrplot to make large correlation plots
GGally to make smaller correlation plots
tidymodels to load in a set of packages (broom, dials, infer, parsnip, purrr, recipes, rsample, tibble, yardstick)
rsample to split the data into testing and training sets; to split the training set for cross-validation
recipes to pre-process data for modeling in a tidy and reproducible way and to extract pre-processed data (major functions are recipe() , prep() and various transformation step_*() functions, as well as juice() - extracts final pre-processed training data and bake() - applies recipe steps to testing data). See here for more info.
parsnip an interface to create models (major functions are fit(), set_engine())
yardstick to evaluate the performance of models
broom to get tidy output for our model fit and performance
ggplot2 to make visualizations with multiple layers
dials to specify hyper-parameter tuning
tune to perform cross validation, tune hyper-parameters, and get performance metrics
workflows to create modeling workflow to streamline the modeling process
vip to create variable importance plots
randomForest to perform the random forest analysis
doParallel to fit cross validation samples in parallel
stringr to manipulate the text the map data
tidyr to separate data within a column into multiple columns
rnaturalearth to get the geometry data for the earth to plot the US
maps to get map database data about counties to draw them on our US map
sf to convert the map data into a data frame
lwgeom to use the sf function to convert map geographical data
rgeos to use geometry data
patchwork to allow plots to be combined

The first time we use a function, we will use the :: to indicate which package we are using. Unless we have overlapping function names, this is not necessary, but we will include it here to be informative about where the functions we will use come from.

Context


The State of Global Air is a report released every year to communicate the impact of air pollution on public health.

The State of Global Air 2019 report which uses data from 2017 stated that:

Air pollution is the fifth leading risk factor for mortality worldwide. It is responsible for more deaths than many better-known risk factors such as malnutrition, alcohol use, and physical inactivity. Each year, more people die from air pollution–related disease than from road traffic injuries or malaria.

[source]

The report also stated that:

In 2017, air pollution is estimated to have contributed to close to 5 million deaths globally — nearly 1 in every 10 deaths.

[source]

The State of Global Air 2018 report using data from 2016 which separated different types of air pollution, found that particulate pollution was particularly associated with mortality.

The 2019 report shows that the highest levels of fine particulate pollution occurs in Africa and Asia and that:

More than 90% of people worldwide live in areas exceeding the World Health Organization (WHO) Guideline for healthy air. More than half live in areas that do not even meet WHO’s least-stringent air quality target.

[source]

Looking at the US specifically, air pollution levels are generally improving, with declining national air pollutant concentration averages as shown from the 2019 Our Nation’s Air report from the US Environmental Protection Agency (EPA):

[source]

However, air pollution continues to contribute to health risk for Americans, in particular in regions with higher than national average rates of pollution that actually at time exceed the WHO’s recommended level. Thus, it is important to obtain high spatial granularity in estimates of air pollution in order to identify locations where populations are experiencing harmful levels of exposure.

You can see that current air quality conditions at this website and you will notice variation across different cities.

For example, here are the conditions in Topeka Kansas at the time this case study was created:

It reports particulate values using what is called the Air Quality Index (AQI). This calculator indicates that 114 AQI is equivalent to 40.7 ug/m3 and is considered unhealthy for sensitive individuals. Thus, some areas exceed the WHO annual exposure guideline (10 ug/m3) and this may adversely affect the health of people living in these locations.

Adverse health effects have been associated with populations experiencing higher pollution exposure despite the levels being below suggested guidelines. Also, it appears that the composition of the particulate mater and the influence of other demographic factors may make specific populations more at risk for adverse health effects due to air pollution. For example, see this article for more details.

The monitor data that we will use in this case study come from a system of monitors in which roughly 90% are located within cities. Hence, there is an equity issue in terms of capturing the air pollution levels of more rural areas. To get a better sense of the pollution exposures for the individuals living in these areas, methods like machine learning can be useful to estimate air pollution levels in areas with little to no monitoring. Specifically, these methods can be used to estimate air pollution in these low monitoring areas so that we can make a map like this where we have annual estimates for all of the contiguous US:

[source]

This is what we aim to achieve in this case study.

Limitations


There are some important considerations regarding the data analysis in this case study to keep in mind:

  1. The data do not include information about the composition of particulate mater. Different types of particulates may be more benign or deleterious for health outcomes.

  2. Outdoor pollution levels are not necessarily an indication of individual exposures. People spend differing amounts of time indoors and outdoors and are exposed to different pollution levels indoors. Researchers are now developing personal monitoring systems to track air pollution levels on the personal level.

  3. Our analysis will use annual mean estimates of pollution levels, but these can vary greatly by season, day and even hour. There are data sources that have finer levels of temporal data, however we are interested in long term exposures, as these appear to be the most influential for health outcomes, so we chose to use annual level data.

What are the data?


When using machine learning for prediction, there are two main types of data of interest:

  1. An continuous outcome variable that we want to predict
  2. A set of feature(s) (or predictor variables) that we use to predict the outcome variable

The outcome variable is what are trying to predict. To build (or train) our model, we use both the outcome and features. The goal is to identify informative features that can explain a large amount of variation in our outcome variable. Using this model, we can then predict the outcome from new observations with the same features where have not observed the outcome.

As a simple example, imagine that we have data about the sales and characteristics of cars from last year and we want to predict which cars might sell well this year. We do not have the sales data yet for this year, but we do know the characteristics of our cars for this year. We can build a model of the characteristics that explained sales last year to estimate what cars might sell well this year. In this case, our outcome variable is the sales of cars, while the different characteristics of the cars make up our features.

Start with a question

This is the most commonly missed step when developing a machine learning algorithm. Machine learning can very easily be turned into an engineering problem. Just dump the outcome and the features into a black box algorithm and viola! But this kind of thinking can lead to major problems. In general good machine learning questions:

  1. Have a plausible explanation for why the features predict the outcome.
  2. Consider potential variation in both the features and the outcome over time
  3. Are consistently re-evaluated on criteria 1 and 2 over time.

In this case study, we want to predict air pollution levels. To build this machine learning algorithm, our outcome variable is fine particulate matter (PM2.5) captured from air pollution monitors in the contiguous US from 2008. Our features (or predictor variables) include data about population density, road density, urbanization levels, and NASA satellite data.

All of our data was previously collected by a researcher at the Johns Hopkins School of Public Health who studies air pollution and climate change.

Our outcome variable

The monitor data that we will be using comes from gravimetric monitors (see picture below) operated by the US Enivornmental Protection Agency (EPA).

These monitors use a filtration system to specifically capture fine particulate matter.

[source]

The weight of this particulate matter is manually measured daily or weekly. For the EPA standard operating procedure for PM gravimetric analysis in 2008, we refer the reader to here.

For more on Gravimetric analysis, you can expand here

Gravimetric analysis is also used for emission testing. The same idea applies: a fresh filter is applied and the desired amount of time passes, then the filter is removed and weighed.

There are other monitoring systems that can provide hourly measurements, but we will not be using data from these monitors in our analysis. Gravimetric analysis is considered to be among the most accurate methods for measuring particulate matter.

In our data set, the value column indicates the PM2.5 monitor average for 2008 in mass of fine particles/volume of air for 876 gravimetric monitors. The units are micrograms of fine particulate mater (PM) that is less than 2.5 micrometers in diameter per cubic meter of air - mass concentration (ug/m3). Recall the WHO exposure guideline is < 10 ug/m3 on average annually for PM2.5.

Our features (predictor variables)

There are 48 features with values for each of the 876 monitors (observations). The data comes from the US Enivornmental Protection Agency (EPA), the National Aeronautics and Space Administration (NASA), the US Census, and the National Center for Health Statistics (NCHS).

Click here to see a table about the set of features

Variable Details
id Monitor number
– the county number is indicated before the decimal
– the monitor number is indicated after the decimal
Example: 1073.0023 is Jefferson county (1073) and .0023 one of 8 monitors
fips Federal information processing standard number for the county where the monitor is located
– 5 digit id code for counties (zero is often the first value and sometimes is not shown)
– the first 2 numbers indicate the state
– the last three numbers indicate the county
Example: Alabama’s state code is 01 because it is first alphabetically
(note: Alaska and Hawaii are not included because they are not part of the contiguous US)
Lat Latitude of the monitor in degrees
Lon Longitude of the monitor in degrees
state State where the monitor is located
county County where the monitor is located
city City where the monitor is located
CMAQ Estimated values of air pollution from a computational model called Community Multiscale Air Quality (CMAQ)
– A monitoring system that simulates the physics of the atmosphere using chemistry and weather data to predict the air pollution
Does not use any of the PM2.5 gravimetric monitoring data. (There is a version that does use the gravimetric monitoring data, but not this one!)
– Data from the EPA
zcta Zip Code Tabulation Area where the monitor is located
– Postal Zip codes are converted into “generalized areal representations” that are non-overlapping
– Data from the 2010 Census
zcta_area Land area of the zip code area in meters squared
– Data from the 2010 Census
zcta_pop Population in the zip code area
– Data from the 2010 Census
imp_a500 Impervious surface measure
– Within a circle with a radius of 500 meters around the monitor
– Impervious surface are roads, concrete, parking lots, buildings
– This is a measure of development
imp_a1000 Impervious surface measure
– Within a circle with a radius of 1000 meters around the monitor
imp_a5000 Impervious surface measure
– Within a circle with a radius of 5000 meters around the monitor
imp_a10000 Impervious surface measure
– Within a circle with a radius of 10000 meters around the monitor
imp_a15000 Impervious surface measure
– Within a circle with a radius of 15000 meters around the monitor
county_area Land area of the county of the monitor in meters squared
county_pop Population of the county of the monitor
Log_dist_to_prisec Log (Natural log) distance to a primary or secondary road from the monitor
– Highway or major road
log_pri_length_5000 Count of primary road length in meters in a circle with a radius of 5000 meters around the monitor (Natural log)
– Highways only
log_pri_length_10000 Count of primary road length in meters in a circle with a radius of 10000 meters around the monitor (Natural log)
– Highways only
log_pri_length_15000 Count of primary road length in meters in a circle with a radius of 15000 meters around the monitor (Natural log)
– Highways only
log_pri_length_25000 Count of primary road length in meters in a circle with a radius of 25000 meters around the monitor (Natural log)
– Highways only
log_prisec_length_500 Count of primary and secondary road length in meters in a circle with a radius of 500 meters around the monitor (Natural log)
– Highway and secondary roads
log_prisec_length_1000 Count of primary and secondary road length in meters in a circle with a radius of 1000 meters around the monitor (Natural log)
– Highway and secondary roads
log_prisec_length_5000 Count of primary and secondary road length in meters in a circle with a radius of 5000 meters around the monitor (Natural log)
– Highway and secondary roads
log_prisec_length_10000 Count of primary and secondary road length in meters in a circle with a radius of 10000 meters around the monitor (Natural log)
– Highway and secondary roads
log_prisec_length_15000 Count of primary and secondary road length in meters in a circle with a radius of 15000 meters around the monitor (Natural log)
– Highway and secondary roads
log_prisec_length_25000 Count of primary and secondary road length in meters in a circle with a radius of 25000 meters around the monitor (Natural log)
– Highway and secondary roads
log_nei_2008_pm25_sum_10000 Tons of emissions from major sources data base (annual data) sum of all sources within a circle with a radius of 10000 meters of distance around the monitor (Natural log)
log_nei_2008_pm25_sum_15000 Tons of emissions from major sources data base (annual data) sum of all sources within a circle with a radius of 15000 meters of distance around the monitor (Natural log)
log_nei_2008_pm25_sum_25000 Tons of emissions from major sources data base (annual data) sum of all sources within a circle with a radius of 25000 meters of distance around the monitor (Natural log)
log_nei_2008_pm10_sum_10000 Tons of emissions from major sources data base (annual data) sum of all sources within a circle with a radius of 10000 meters of distance around the monitor (Natural log)
log_nei_2008_pm10_sum_15000 Tons of emissions from major sources data base (annual data) sum of all sources within a circle with a radius of 15000 meters of distance around the monitor (Natural log)
log_nei_2008_pm10_sum_25000 Tons of emissions from major sources data base (annual data) sum of all sources within a circle with a radius of 25000 meters of distance around the monitor (Natural log)
popdens_county Population density (number of people per kilometer squared area of the county)
popdens_zcta Population density (number of people per kilometer squared area of zcta)
nohs Percentage of people in zcta area where the monitor is that do not have a high school degree
– Data from the Census
somehs Percentage of people in zcta area where the monitor whose highest formal educational attainment was some high school education
– Data from the Census
hs Percentage of people in zcta area where the monitor whose highest formal educational attainment was completing a high school degree
– Data from the Census
somecollege Percentage of people in zcta area where the monitor whose highest formal educational attainment was completing some college education
– Data from the Census
associate Percentage of people in zcta area where the monitor whose highest formal educational attainment was completing an associate degree
– Data from the Census
bachelor Percentage of people in zcta area where the monitor whose highest formal educational attainment was a bachelor’s degree
– Data from the Census
grad Percentage of people in zcta area where the monitor whose highest formal educational attainment was a graduate degree
– Data from the Census
pov Percentage of people in zcta area where the monitor is that lived in poverty in 2008 - or would it have been 2007 guidelines??https://aspe.hhs.gov/2007-hhs-poverty-guidelines
– Data from the Census
hs_orless Percentage of people in zcta area where the monitor whose highest formal educational attainment was a high school degree or less (sum of nohs, somehs, and hs)
urc2013 2013 Urban-rural classification of the county where the monitor is located
– 6 category variable - 1 is totally urban 6 is completely rural
– Data from the National Center for Health Statistics](https://www.cdc.gov/nchs/index.htm){target="_blank"}
urc2006 2006 Urban-rural classification of the county where the monitor is located
– 6 category variable - 1 is totally urban 6 is completely rural
– Data from the National Center for Health Statistics
aod Aerosol Optical Depth measurement from a NASA satellite
– based on the diffraction of a laser
– used as a proxy of particulate pollution
– unit-less - higher value indicates more pollution
– Data from NASA

Many of these features have to do with the circular area around the monitor called the “buffer”. These are illustrated in the following figure:

Data Import


All of our data was previously collected by a researcher at the Johns Hopkins School of Public Health who studies air pollution and climate change.

We have one CSV file that contains both our single outcome variable and all of our features (or predictor variables).

Next, we import our data into R now so that we can explore the data further. We will call our data object pm for particulate matter. We import the data using the read_csv() function from the readr package.

Data Exploration and Wrangling


The first step in performing any data analysis is to explore the data.

For example, we might want to better understand the variables included in the data, as we may learn about important details about the data that we should keep in mind as we try to predict our outcome variable.

First, let’s just get a general sense of our data. We can do that using the glimpse() function of the dplyr package (it is also in the tibble package).

We will also use the %>% pipe, which can be used to define the input for later sequential steps.

This will make more sense when we have multiple sequential steps using the same data object.

To use the pipe notation we need to install and load dplyr as well.

For example, here we start with pm data object and “pipe” the object into as input into the glimpse() function. The output is is an overview of what is in the pm object such as the number of rows and columns, all the column names, the data types for each column and the first view values in each column. The output below is scrollable so you can see everything from the glimpse() function.

Rows: 876
Columns: 50
$ id                          <dbl> 1003.001, 1027.000, 1033.100, 1049.100, 1…
$ value                       <dbl> 9.597647, 10.800000, 11.212174, 11.659091…
$ fips                        <dbl> 1003, 1027, 1033, 1049, 1055, 1069, 1073,…
$ lat                         <dbl> 30.49800, 33.28126, 34.75878, 34.28763, 3…
$ lon                         <dbl> -87.88141, -85.80218, -87.65056, -85.9683…
$ state                       <chr> "Alabama", "Alabama", "Alabama", "Alabama…
$ county                      <chr> "Baldwin", "Clay", "Colbert", "DeKalb", "…
$ city                        <chr> "Fairhope", "Ashland", "Muscle Shoals", "…
$ CMAQ                        <dbl> 8.098836, 9.766208, 9.402679, 8.534772, 9…
$ zcta                        <dbl> 36532, 36251, 35660, 35962, 35901, 36303,…
$ zcta_area                   <dbl> 190980522, 374132430, 16716984, 203836235…
$ zcta_pop                    <dbl> 27829, 5103, 9042, 8300, 20045, 30217, 90…
$ imp_a500                    <dbl> 0.01730104, 1.96972318, 19.17301038, 5.78…
$ imp_a1000                   <dbl> 1.4096021, 0.8531574, 11.1448962, 3.86764…
$ imp_a5000                   <dbl> 3.3360118, 0.9851479, 15.1786154, 1.23114…
$ imp_a10000                  <dbl> 1.9879187, 0.5208189, 9.7253870, 1.031646…
$ imp_a15000                  <dbl> 1.4386207, 0.3359198, 5.2472094, 0.973044…
$ county_area                 <dbl> 4117521611, 1564252280, 1534877333, 20126…
$ county_pop                  <dbl> 182265, 13932, 54428, 71109, 104430, 1015…
$ log_dist_to_prisec          <dbl> 4.648181, 7.219907, 5.760131, 3.721489, 5…
$ log_pri_length_5000         <dbl> 8.517193, 8.517193, 8.517193, 8.517193, 9…
$ log_pri_length_10000        <dbl> 9.210340, 9.210340, 9.274303, 10.409411, …
$ log_pri_length_15000        <dbl> 9.630228, 9.615805, 9.658899, 11.173626, …
$ log_pri_length_25000        <dbl> 11.32735, 10.12663, 10.15769, 11.90959, 1…
$ log_prisec_length_500       <dbl> 7.295356, 6.214608, 8.611945, 7.310155, 8…
$ log_prisec_length_1000      <dbl> 8.195119, 7.600902, 9.735569, 8.585843, 9…
$ log_prisec_length_5000      <dbl> 10.815042, 10.170878, 11.770407, 10.21420…
$ log_prisec_length_10000     <dbl> 11.88680, 11.40554, 12.84066, 11.50894, 1…
$ log_prisec_length_15000     <dbl> 12.205723, 12.042963, 13.282656, 12.35366…
$ log_prisec_length_25000     <dbl> 13.41395, 12.79980, 13.79973, 13.55979, 1…
$ log_nei_2008_pm25_sum_10000 <dbl> 0.318035438, 3.218632928, 6.573127301, 0.…
$ log_nei_2008_pm25_sum_15000 <dbl> 1.967358961, 3.218632928, 6.581917457, 3.…
$ log_nei_2008_pm25_sum_25000 <dbl> 5.067308, 3.218633, 6.875900, 4.887665, 4…
$ log_nei_2008_pm10_sum_10000 <dbl> 1.35588511, 3.31111648, 6.69187313, 0.000…
$ log_nei_2008_pm10_sum_15000 <dbl> 2.26783411, 3.31111648, 6.70127741, 3.350…
$ log_nei_2008_pm10_sum_25000 <dbl> 5.628728, 3.311116, 7.148858, 5.171920, 4…
$ popdens_county              <dbl> 44.265706, 8.906492, 35.460814, 35.330814…
$ popdens_zcta                <dbl> 145.716431, 13.639555, 540.887040, 40.718…
$ nohs                        <dbl> 3.3, 11.6, 7.3, 14.3, 4.3, 5.8, 7.1, 2.7,…
$ somehs                      <dbl> 4.9, 19.1, 15.8, 16.7, 13.3, 11.6, 17.1, …
$ hs                          <dbl> 25.1, 33.9, 30.6, 35.0, 27.8, 29.8, 37.2,…
$ somecollege                 <dbl> 19.7, 18.8, 20.9, 14.9, 29.2, 21.4, 23.5,…
$ associate                   <dbl> 8.2, 8.0, 7.6, 5.5, 10.1, 7.9, 7.3, 8.0, …
$ bachelor                    <dbl> 25.3, 5.5, 12.7, 7.9, 10.0, 13.7, 5.9, 17…
$ grad                        <dbl> 13.5, 3.1, 5.1, 5.8, 5.4, 9.8, 2.0, 8.7, …
$ pov                         <dbl> 6.1, 19.5, 19.0, 13.8, 8.8, 15.6, 25.5, 7…
$ hs_orless                   <dbl> 33.3, 64.6, 53.7, 66.0, 45.4, 47.2, 61.4,…
$ urc2013                     <dbl> 4, 6, 4, 6, 4, 4, 1, 1, 1, 1, 1, 1, 1, 2,…
$ urc2006                     <dbl> 5, 6, 4, 5, 4, 4, 1, 1, 1, 1, 1, 1, 1, 2,…
$ aod                         <dbl> 37.36364, 34.81818, 36.00000, 33.08333, 4…

We can see that there are 876 monitors (rows) and that we have 50 total variables (columns) - one of which is the outcome variable. In this case, the outcome variable is called value.

AVOCADO: Should we link to a code book here? Like could we use this as a teaching opportunity to demonstrate the purpose of a code book so a student knows how we know the PM values are value?

avocado response: there isnt a code book related to this data, Roger wrangled it for me, but the vaping case study actually uses code books for that data. Would you like to link to a generic code book and describe that process? I did however add information about Roger and how he previously collected the data.

Notice that some of the variables that we would think of as factors (or categorical data) are currently of class character as indicated by the <chr> just to the right of the column names/variable names in the glimpse() output. This means that the variable values are character strings, such as words or phrases.

The other variables are of class <dbl>, which stands for double precision which indicates that the are numeric and that they have decimal values. In contrast, one could have integer values which would not allow for decimal numbers. Here is a link for more information on double precision numeric values.

Another common data class is factor which is abbreviated like this: <fct>. A factor is something that has unique levels but there is no appreciable order to the levels. For example we can have a numeric value that is just an id that we want to be interpreted as just a unique level and not as the number that it would typically indicate. This would be useful for several of our variables:

  1. the monitor ID (id)
  2. the Federal Information Processing Standard number for the county where the monitor was located (fips)
  3. the zip code tabulation area (zcta)

None of the values actually have any real numeric meaning, so we want to make sure that R does not interpret them as if they do.

So let’s convert these variables into factors. We can do this using the across() function of the dplyr package and the as.factor() base function. The across() function has two main arguments: (i) the columns you want to operate on and (ii) the function or list of functions to apply to each column.

In this case, we are also using the magrittr assignment pipe or double pipe that looks like this %<>% of the magrittr package. This allows us use the pm data as input, but also reassigns the output to the same data object name.

Rows: 876
Columns: 50
$ id                          <fct> 1003.001, 1027.0001, 1033.1002, 1049.1003…
$ value                       <dbl> 9.597647, 10.800000, 11.212174, 11.659091…
$ fips                        <fct> 1003, 1027, 1033, 1049, 1055, 1069, 1073,…
$ lat                         <dbl> 30.49800, 33.28126, 34.75878, 34.28763, 3…
$ lon                         <dbl> -87.88141, -85.80218, -87.65056, -85.9683…
$ state                       <chr> "Alabama", "Alabama", "Alabama", "Alabama…
$ county                      <chr> "Baldwin", "Clay", "Colbert", "DeKalb", "…
$ city                        <chr> "Fairhope", "Ashland", "Muscle Shoals", "…
$ CMAQ                        <dbl> 8.098836, 9.766208, 9.402679, 8.534772, 9…
$ zcta                        <fct> 36532, 36251, 35660, 35962, 35901, 36303,…
$ zcta_area                   <dbl> 190980522, 374132430, 16716984, 203836235…
$ zcta_pop                    <dbl> 27829, 5103, 9042, 8300, 20045, 30217, 90…
$ imp_a500                    <dbl> 0.01730104, 1.96972318, 19.17301038, 5.78…
$ imp_a1000                   <dbl> 1.4096021, 0.8531574, 11.1448962, 3.86764…
$ imp_a5000                   <dbl> 3.3360118, 0.9851479, 15.1786154, 1.23114…
$ imp_a10000                  <dbl> 1.9879187, 0.5208189, 9.7253870, 1.031646…
$ imp_a15000                  <dbl> 1.4386207, 0.3359198, 5.2472094, 0.973044…
$ county_area                 <dbl> 4117521611, 1564252280, 1534877333, 20126…
$ county_pop                  <dbl> 182265, 13932, 54428, 71109, 104430, 1015…
$ log_dist_to_prisec          <dbl> 4.648181, 7.219907, 5.760131, 3.721489, 5…
$ log_pri_length_5000         <dbl> 8.517193, 8.517193, 8.517193, 8.517193, 9…
$ log_pri_length_10000        <dbl> 9.210340, 9.210340, 9.274303, 10.409411, …
$ log_pri_length_15000        <dbl> 9.630228, 9.615805, 9.658899, 11.173626, …
$ log_pri_length_25000        <dbl> 11.32735, 10.12663, 10.15769, 11.90959, 1…
$ log_prisec_length_500       <dbl> 7.295356, 6.214608, 8.611945, 7.310155, 8…
$ log_prisec_length_1000      <dbl> 8.195119, 7.600902, 9.735569, 8.585843, 9…
$ log_prisec_length_5000      <dbl> 10.815042, 10.170878, 11.770407, 10.21420…
$ log_prisec_length_10000     <dbl> 11.88680, 11.40554, 12.84066, 11.50894, 1…
$ log_prisec_length_15000     <dbl> 12.205723, 12.042963, 13.282656, 12.35366…
$ log_prisec_length_25000     <dbl> 13.41395, 12.79980, 13.79973, 13.55979, 1…
$ log_nei_2008_pm25_sum_10000 <dbl> 0.318035438, 3.218632928, 6.573127301, 0.…
$ log_nei_2008_pm25_sum_15000 <dbl> 1.967358961, 3.218632928, 6.581917457, 3.…
$ log_nei_2008_pm25_sum_25000 <dbl> 5.067308, 3.218633, 6.875900, 4.887665, 4…
$ log_nei_2008_pm10_sum_10000 <dbl> 1.35588511, 3.31111648, 6.69187313, 0.000…
$ log_nei_2008_pm10_sum_15000 <dbl> 2.26783411, 3.31111648, 6.70127741, 3.350…
$ log_nei_2008_pm10_sum_25000 <dbl> 5.628728, 3.311116, 7.148858, 5.171920, 4…
$ popdens_county              <dbl> 44.265706, 8.906492, 35.460814, 35.330814…
$ popdens_zcta                <dbl> 145.716431, 13.639555, 540.887040, 40.718…
$ nohs                        <dbl> 3.3, 11.6, 7.3, 14.3, 4.3, 5.8, 7.1, 2.7,…
$ somehs                      <dbl> 4.9, 19.1, 15.8, 16.7, 13.3, 11.6, 17.1, …
$ hs                          <dbl> 25.1, 33.9, 30.6, 35.0, 27.8, 29.8, 37.2,…
$ somecollege                 <dbl> 19.7, 18.8, 20.9, 14.9, 29.2, 21.4, 23.5,…
$ associate                   <dbl> 8.2, 8.0, 7.6, 5.5, 10.1, 7.9, 7.3, 8.0, …
$ bachelor                    <dbl> 25.3, 5.5, 12.7, 7.9, 10.0, 13.7, 5.9, 17…
$ grad                        <dbl> 13.5, 3.1, 5.1, 5.8, 5.4, 9.8, 2.0, 8.7, …
$ pov                         <dbl> 6.1, 19.5, 19.0, 13.8, 8.8, 15.6, 25.5, 7…
$ hs_orless                   <dbl> 33.3, 64.6, 53.7, 66.0, 45.4, 47.2, 61.4,…
$ urc2013                     <dbl> 4, 6, 4, 6, 4, 4, 1, 1, 1, 1, 1, 1, 1, 2,…
$ urc2006                     <dbl> 5, 6, 4, 5, 4, 4, 1, 1, 1, 1, 1, 1, 1, 2,…
$ aod                         <dbl> 37.36364, 34.81818, 36.00000, 33.08333, 4…

Great! Now we can see that these variables are now factors as indicated by <fct> after the variable name.

Skim package

The skim() function of the skimr package is also really helpful for getting a general sense of your data. By design, it provides summary statistics about variables in the data set.

Data summary
Name pm
Number of rows 876
Number of columns 50
_______________________
Column type frequency:
character 3
factor 3
numeric 44
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
state 0 1 4 20 0 49 0
county 0 1 3 20 0 471 0
city 0 1 4 48 0 607 0

Variable type: factor

skim_variable n_missing complete_rate ordered n_unique top_counts
id 0 1 FALSE 876 100: 1, 102: 1, 103: 1, 104: 1
fips 0 1 FALSE 569 170: 12, 603: 10, 261: 9, 107: 8
zcta 0 1 FALSE 842 475: 3, 110: 2, 160: 2, 290: 2

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
value 0 1 10.81 2.580000e+00 3.02 9.27 11.15 12.37 2.316000e+01 ▂▆▇▁▁
lat 0 1 38.48 4.620000e+00 25.47 35.03 39.30 41.66 4.840000e+01 ▁▃▅▇▂
lon 0 1 -91.74 1.496000e+01 -124.18 -99.16 -87.47 -80.69 -6.804000e+01 ▃▂▃▇▃
CMAQ 0 1 8.41 2.970000e+00 1.63 6.53 8.62 10.24 2.313000e+01 ▃▇▃▁▁
zcta_area 0 1 183173481.91 5.425989e+08 15459.00 14204601.75 37653560.50 160041508.25 8.164821e+09 ▇▁▁▁▁
zcta_pop 0 1 24227.58 1.777216e+04 0.00 9797.00 22014.00 35004.75 9.539700e+04 ▇▇▃▁▁
imp_a500 0 1 24.72 1.934000e+01 0.00 3.70 25.12 40.22 6.961000e+01 ▇▅▆▃▂
imp_a1000 0 1 24.26 1.802000e+01 0.00 5.32 24.53 38.59 6.750000e+01 ▇▅▆▃▁
imp_a5000 0 1 19.93 1.472000e+01 0.05 6.79 19.07 30.11 7.460000e+01 ▇▆▃▁▁
imp_a10000 0 1 15.82 1.381000e+01 0.09 4.54 12.36 24.17 7.209000e+01 ▇▃▂▁▁
imp_a15000 0 1 13.43 1.312000e+01 0.11 3.24 9.67 20.55 7.110000e+01 ▇▃▁▁▁
county_area 0 1 3768701992.12 6.212830e+09 33703512.00 1116536297.50 1690826566.50 2878192209.00 5.194723e+10 ▇▁▁▁▁
county_pop 0 1 687298.44 1.293489e+06 783.00 100948.00 280730.50 743159.00 9.818605e+06 ▇▁▁▁▁
log_dist_to_prisec 0 1 6.19 1.410000e+00 -1.46 5.43 6.36 7.15 1.045000e+01 ▁▁▃▇▁
log_pri_length_5000 0 1 9.82 1.080000e+00 8.52 8.52 10.05 10.73 1.205000e+01 ▇▂▆▅▂
log_pri_length_10000 0 1 10.92 1.130000e+00 9.21 9.80 11.17 11.83 1.302000e+01 ▇▂▇▇▃
log_pri_length_15000 0 1 11.50 1.150000e+00 9.62 10.87 11.72 12.40 1.359000e+01 ▆▂▇▇▃
log_pri_length_25000 0 1 12.24 1.100000e+00 10.13 11.69 12.46 13.05 1.436000e+01 ▅▃▇▇▃
log_prisec_length_500 0 1 6.99 9.500000e-01 6.21 6.21 6.21 7.82 9.400000e+00 ▇▁▂▂▁
log_prisec_length_1000 0 1 8.56 7.900000e-01 7.60 7.60 8.66 9.20 1.047000e+01 ▇▅▆▃▁
log_prisec_length_5000 0 1 11.28 7.800000e-01 8.52 10.91 11.42 11.83 1.278000e+01 ▁▁▃▇▃
log_prisec_length_10000 0 1 12.41 7.300000e-01 9.21 11.99 12.53 12.94 1.385000e+01 ▁▁▃▇▅
log_prisec_length_15000 0 1 13.03 7.200000e-01 9.62 12.59 13.13 13.57 1.441000e+01 ▁▁▃▇▅
log_prisec_length_25000 0 1 13.82 7.000000e-01 10.13 13.38 13.92 14.35 1.523000e+01 ▁▁▃▇▆
log_nei_2008_pm25_sum_10000 0 1 3.97 2.350000e+00 0.00 2.15 4.29 5.69 9.120000e+00 ▆▅▇▆▂
log_nei_2008_pm25_sum_15000 0 1 4.72 2.250000e+00 0.00 3.47 5.00 6.35 9.420000e+00 ▃▃▇▇▂
log_nei_2008_pm25_sum_25000 0 1 5.67 2.110000e+00 0.00 4.66 5.91 7.28 9.650000e+00 ▂▂▇▇▃
log_nei_2008_pm10_sum_10000 0 1 4.35 2.320000e+00 0.00 2.69 4.62 6.07 9.340000e+00 ▅▅▇▇▂
log_nei_2008_pm10_sum_15000 0 1 5.10 2.180000e+00 0.00 3.87 5.39 6.72 9.710000e+00 ▂▃▇▇▂
log_nei_2008_pm10_sum_25000 0 1 6.07 2.010000e+00 0.00 5.10 6.37 7.52 9.880000e+00 ▁▂▆▇▃
popdens_county 0 1 551.76 1.711510e+03 0.26 40.77 156.67 510.81 2.682191e+04 ▇▁▁▁▁
popdens_zcta 0 1 1279.66 2.757490e+03 0.00 101.15 610.35 1382.52 3.041884e+04 ▇▁▁▁▁
nohs 0 1 6.99 7.210000e+00 0.00 2.70 5.10 8.80 1.000000e+02 ▇▁▁▁▁
somehs 0 1 10.17 6.200000e+00 0.00 5.90 9.40 13.90 7.220000e+01 ▇▂▁▁▁
hs 0 1 30.32 1.140000e+01 0.00 23.80 30.75 36.10 1.000000e+02 ▂▇▂▁▁
somecollege 0 1 21.58 8.600000e+00 0.00 17.50 21.30 24.70 1.000000e+02 ▆▇▁▁▁
associate 0 1 7.13 4.010000e+00 0.00 4.90 7.10 8.80 7.140000e+01 ▇▁▁▁▁
bachelor 0 1 14.90 9.710000e+00 0.00 8.80 12.95 19.22 1.000000e+02 ▇▂▁▁▁
grad 0 1 8.91 8.650000e+00 0.00 3.90 6.70 11.00 1.000000e+02 ▇▁▁▁▁
pov 0 1 14.95 1.133000e+01 0.00 6.50 12.10 21.22 6.590000e+01 ▇▅▂▁▁
hs_orless 0 1 47.48 1.675000e+01 0.00 37.92 48.65 59.10 1.000000e+02 ▁▃▇▃▁
urc2013 0 1 2.92 1.520000e+00 1.00 2.00 3.00 4.00 6.000000e+00 ▇▅▃▂▁
urc2006 0 1 2.97 1.520000e+00 1.00 2.00 3.00 4.00 6.000000e+00 ▇▅▃▂▁
aod 0 1 43.70 1.956000e+01 5.00 31.66 40.17 49.67 1.430000e+02 ▃▇▁▁▁

Notice how there is a column called n_missing about the number of values that are missing.

This is also indicated by the complete_rate variable (or missing/number of observations).

In our data set, it looks like our data do not contain any missing data.

Also notice how the function provides separate tables of summary statistics for each data type: character, factor and numeric.

Next, the n_unqiue column shows us the number of unique values for each of our columns. We can see that there are 49 states represented in the data.

We can see that for many variables there are many low values as the distribution shows two peaks, one near zero and another with a higher value.

This is true for the imp variables (measures of development), the nei variables (measures of emission sources) and the road density variables.

We can also see that the range of some of the variables is very large, in particular the area and population related variables.

Let’s take a look to see which states are included using the distinct() function of the dplyr package:

Scroll through the output:

# A tibble: 49 x 1
   state               
   <chr>               
 1 Alabama             
 2 Arizona             
 3 Arkansas            
 4 California          
 5 Colorado            
 6 Connecticut         
 7 Delaware            
 8 District Of Columbia
 9 Florida             
10 Georgia             
11 Idaho               
12 Illinois            
13 Indiana             
14 Iowa                
15 Kansas              
16 Kentucky            
17 Louisiana           
18 Maine               
19 Maryland            
20 Massachusetts       
21 Michigan            
22 Minnesota           
23 Mississippi         
24 Missouri            
25 Montana             
26 Nebraska            
27 Nevada              
28 New Hampshire       
29 New Jersey          
30 New Mexico          
31 New York            
32 North Carolina      
33 North Dakota        
34 Ohio                
35 Oklahoma            
36 Oregon              
37 Pennsylvania        
38 Rhode Island        
39 South Carolina      
40 South Dakota        
41 Tennessee           
42 Texas               
43 Utah                
44 Vermont             
45 Virginia            
46 Washington          
47 West Virginia       
48 Wisconsin           
49 Wyoming             

It looks like “District of Columbia” is being included as a state. We can see that Alaska and Hawaii are not included in the data.

Evaluate correlation

In prediction analyses, it is also useful to evaluate if any of the variables are correlated. Why should we care about this?

If we are using a linear regression to model our data then we might run into a problem called multicolinearity which can lead us to misinterpret what is really predictive of our outcome variable. This phenomenon occurs when the predictor variables actually predict one another. See this case study for a deeper explanation about this.

Another reason we should look out for correlation is that we don’t want to include redundant variables. This can add unnecessary noise to our algorithm causing a reduction in prediction accuracy and it can cause our algorithm to be unnecessarily slower. Finally, it can also make it difficult to interpret what variables are actually predictive.

Intuitively we can expect some of our variables to be correlated.

Let’s first take a look at all of our numeric variables with thecorrplot package: The corrplot package is another option to look at correlation among possible predictors, and particularly useful if we have many predictors.

First, we calculate the Pearson correlation coefficients between all features pairwise using the cor() function of the stats package (which is loaded automatically). Then we use the corrplot::corrplot() function.

The tl.cex = 0.5 argument controls the size of the text label.

We can also plot the absolute value of the Pearson correlation coefficients using the abs() function from base R and change the order of the columns.

There are several options for ordering the variables. See here for more options. Here we will use the “hclust” option for ordering by hierarchical clustering - which will order the variables by how similar they are to one another.

The cl.lim = c(0, 1) argument limits the color label to be between 0 and 1.

We can see that the development variables (imp) variables are correlated with each other as we might expect. We also see that the road density variables seem to be correlated with each other, and the emission variables seem to be correlated with each other.

Also notice that none of the predictors are highly correlated with our outcome variable (value).

We can take also take a closer look using the ggcorr() function and the ggpairs() function of the GGally package.

To select our variables of interest we can use the select() function with the contains() function of the tidyr package.

First let’s look at the imp/development variables. We can change the default color palette (palette = "RdBu") and add on correlation coefficients to the plot (label = TRUE).

Indeed, we can see that imp_a1000 and imp_a500 are highly correlated, as well as imp_a10000, imp_a15000.

Next, let’s take a look at the road density data:

We can see that many of the road density variables are highly correlated with one another, while others are less so.

Finally let’s look at the emission variables.

We would also expect the population density data might correlate with some of these variables. Let’s take a look.

Interesting, so these variables don’t appear to be highly correlated, therefore we might need variables from each of the categories to predict our monitor PM2.5 pollution values.

Because some variables in our data have extreme values, it might be good to take a log transformation. This can affect our estimates of correlation.

Indeed this increased the correlation, but variables from each of these categories may still prove to be useful for prediction.

Now that we have a sense of what our data are, we can get started with building a machine learning model to predict air pollution.

What is machine learning?


You may have learned about the central dogma of statistics that you sample from a population.

Then you use the sample to try to guess what is happening in the population.

For prediction we have a similar sampling problem

But now we are trying to build a rule that can be used to predict a single observation’s value of some characteristic using characteristics of the other observations.

Let’s make this more concrete.

If you recall from the What are the data? section above, when we are using machine learning for prediction, our data consists of:

  1. An continuous outcome variable that we want to predict
  2. A set of feature(s) (or predictor variables) that we use to predict the outcome variable

We will use \(Y\) to denote the outcome variable and \(X = (X_1, \dots, X_p)\) to denote \(p\) different features (or predictor variables). Because our outcome variable is continuous (as opposed to categorical), we are interested in a particular type of machine learning algorithm.

Our goal is to build a machine learning algorithm that uses the features \(X\) as input and predicts an outcome variable (or air pollution levels) in the situation where we do not know the outcome variable.

The way we do this is to use data where we have both the features \((X_1=x_1, \dots X_p=x_p)\) and the actual outcome \(Y\) data to train a machine learning algorithm to predict the outcome, which we call \(\hat{Y}\).

When we say train a machine learning algorithm we mean that we estimate a function \(f\) that uses the predictor variables \(X\) as input or \(\hat{Y} = f(X)\).

ML as an optimization problem

If we are doing a good job, then our predicted outcome \(\hat{Y}\) should closely match our actual outcome \(Y\) that we observed.

In this way, we can think of machine learning (ML) as an optimization problem that tries to minimize the distance between \(\hat{Y} = f(X)\) and \(Y\).

\[d(Y - f(X))\] The choice of distance metric \(d(\cdot)\) can be the mean of the absolute or squared difference or something more complicated.

Much of the fields of statistics and computer science are focused on defining \(f\) and \(d\).

The parts of an ML problem

To set up a machine learning (ML) problem, we need a few components. To solve a (standard) machine learning problem you need:

  1. A data set to train from.
  2. An algorithm or set of algorithms you can use to try values of \(f\)
  3. A distance metric \(d\) for measuring how close \(Y\) is to \(\hat{Y}\)
  4. A definition of what a “good” distance is

While each of these components is a technical problem, there has been a ton of work addressing those technical details. The most pressing open issue in machine learning is realizing that though these are technical steps they are not objective steps. In other words, how you choose the data, algorithm, metric, and definition of “good” says what you value and can dramatically change the results. A couple of cases where this was a big deal are:

  1. Machine learning for recidivism - people built ML models to predict who would re-commit a crime. But these predictions were based on historically biased data which led to biased predictions about who would commit new crimes.
  2. Deciding how self driving cars should act - self driving cars will have to make decisions about how to drive, who they might injure, and how to avoid accidents. Depending on our choices for \(f\) and \(d\) these might lead to wildly different kinds of self driving cars. Try out the moralmachine to see how this looks in practice.

Now that we know a bit more about machine learning, let’s build a model to predict air pollution levels using the tidymodels framework.

Machine learning with tidymodels


The goal is to build a machine learning algorithm uses the features as input and predicts a outcome variable (or air pollution levels) in the situation where we do not know the outcome variable.

The way we do this is to use data where we have both the input and output data to train a machine learning algorithm.

To train a machine learning algorithm, we will use the tidymodels package ecosystem.

Overview

The tidymodels ecosystem

To perform our analysis we will be using the tidymodels suite of packages. You may be familiar with the older packages caret or mlr which are also for machine learning and modeling but are not a part of the tidyverse. Max Kuhn describes tidymodels like this:

“Other packages, such as caret and mlr, help to solve the R model API issue. These packages do a lot of other things too: pre-processing, model tuning, resampling, feature selection, ensembling, and so on. In the tidyverse, we strive to make our packages modular and parsnip is designed only to solve the interface issue. It is not designed to be a drop-in replacement for caret. The tidymodels package collection, which includes parsnip, has other packages for many of these tasks, and they are designed to work together. We are working towards higher-level APIs that can replicate and extend what the current model packages can do.”

There are many R packages in the tidymodels ecosystem, which assist with various steps in the process of building a machine learning algorithm. These are the main packages, but there are others.

This is a schematic of how these packages work together to build a machine learning algorithm:

Benefits of tidymodels

The two major benefits of tidymodels are:

  1. Standardized workflow/format/notation across different types of machine learning algorithms

Different notations are required for different algorithms as the algorithms have been developed by different people. This would require the painstaking process of reformatting the data to be compatible with each algorithm if multiple algorithms were tested.

  1. Can easily modify pre-processing, algorithm choice, and hyper-parameter tuning making optimization easy

Modifying a piece of the overall process is now easier than before because many of the steps are specified using the tidymodels packages in a convenient manner. Thus the entire process can be rerun after a simple change to pre-processing without much difficulty.

Splitting the data

The first step after data exploration in machine learning analysis is to split the data into training and testing data sets.

The training data set will be used to build and tune our model. This is the data that the model “learns” on. The testing data set will be used to evaluate the performance of our model in a more generalizable way. What do we mean by “generalizable”?

Remember that our main goal is to use our model to be able to predict air pollution levels in areas where there are no gravimetric monitors.

Therefore, if our model is really good at predicting air pollution with the data that we use to build it, it might not do the best job for the areas where there are few to no monitors.

This would cause us to have really good prediction accuracy and we might assume that we were going to do a good job estimating air pollution any time we use our model, but in fact this would likely not be the case. This situation is what we call overfitting .

Overfitting happens when we end up modeling not only the major relationships in our data but also the noise within our data.

[source]

If we get good prediction with our testing set, then we know that our model can be applied to other data and will likely perform well. We will discuss this more later.

We will not touch the testing set until we have completed optimizing our model with the training set. This will allow us to have a less biased evaluation of how well our model can do with other data besides the data used in the training set to build the model. Ideally, you would also want a completely independent data set to further test the performance of your model.

To split the data into training and testing, we will use the initial_split() function in the rsample package to specify how we want to split our data.

<Analysis/Assess/Total>
<584/292/876>

A couple of notes from the code above:

  • Typically, data are split into 3/4 of the observations for training and 1/4 for testing. This is the default proportion and does not need to be specified. However, you can change the proportion using the prop argument, which we will do that here for illustrative purposes.
  • Since the split is performed randomly, it is a good idea to use the set.seed() function in base R to ensure that if your rerun your code that your split will be the same next time.
  • We can see the number of monitors in our training, testing, and original data by typing in the name of our split object. The result will look like this: <training data sample number, testing data sample number, original sample number>

Now, you can also specify a variable to stratify by with the strata argument. This is useful if you have imbalanced categorical variables and you would like to intentionally make sure that there are similar number of samples of the rarer categories in both the testing and training sets. Otherwise the split is performed randomly.

According to the documentation for the rsample package:

The strata argument causes the random sampling to be conducted within the stratification variable. This can help ensure that the number of data points in the training data is equivalent to the proportions in the original data set.

In the case with our data set, perhaps we would like our training set to have similar proportions of monitors from each of the states as in the initial data. This might be useful if we want our model to be generalizable across all of the states.

We can see that indeed there are different proportions of monitors in each state by using the count() function of the dplyr package.

Scroll through the output:

# A tibble: 49 x 2
   state                    n
   <chr>                <int>
 1 Alabama                 24
 2 Arizona                 17
 3 Arkansas                16
 4 California              85
 5 Colorado                15
 6 Connecticut             14
 7 Delaware                 7
 8 District Of Columbia     3
 9 Florida                 29
10 Georgia                 28
11 Idaho                    7
12 Illinois                38
13 Indiana                 36
14 Iowa                    20
15 Kansas                  10
16 Kentucky                22
17 Louisiana               17
18 Maine                    1
19 Maryland                15
20 Massachusetts           16
21 Michigan                30
22 Minnesota               17
23 Mississippi             12
24 Missouri                13
25 Montana                 16
26 Nebraska                 7
27 Nevada                   4
28 New Hampshire            7
29 New Jersey              23
30 New Mexico              10
31 New York                24
32 North Carolina          35
33 North Dakota             4
34 Ohio                    44
35 Oklahoma                10
36 Oregon                  17
37 Pennsylvania            32
38 Rhode Island             5
39 South Carolina          14
40 South Dakota             9
41 Tennessee                3
42 Texas                   27
43 Utah                    14
44 Vermont                  4
45 Virginia                20
46 Washington               8
47 West Virginia           14
48 Wisconsin               21
49 Wyoming                 12

If our data set were large enough it might be nice then to stratify by state using the strata = "state" argument in initial_split(), but our data is unfortunately not large enough.

Importantly the initial_split function only determines what rows of our pm data frame should be assigned for training or testing, it does not actually split the data.

To extract the testing and training data we can use the training() and testing() functions also of the rsample package.

# A tibble: 48 x 2
   state                    n
   <chr>                <int>
 1 Alabama                 18
 2 Arizona                 12
 3 Arkansas                14
 4 California              54
 5 Colorado                12
 6 Connecticut              8
 7 Delaware                 6
 8 District Of Columbia     2
 9 Florida                 18
10 Georgia                 17
# … with 38 more rows
# A tibble: 48 x 2
   state                    n
   <chr>                <int>
 1 Alabama                  6
 2 Arizona                  5
 3 Arkansas                 2
 4 California              31
 5 Colorado                 3
 6 Connecticut              6
 7 Delaware                 1
 8 District Of Columbia     1
 9 Florida                 11
10 Georgia                 11
# … with 38 more rows

Preparing for pre-processing the data

After splitting the data, the next step is to process the training and testing data so that the data are are compatible and optimized to be used with the model. This involves assigning variables to specific roles within the model and pre-processing like scaling variables and removing redundant variables. This process is also called feature engineering.

To do this in tidymodels, we will create what’s called a “recipe” using the recipes package, which is a standardized format for a sequence of steps for pre-processing the data. This can be very useful because it makes testing out different pre-processing steps or different algorithms with the same pre-processing very easy and reproducible. Creating a recipe specifies how a data frame of predictors should be created - it specifies what variables to be used and the pre-processing steps, but it does not execute these steps or create the data frame of predictors.

Step 1: Specify variables roles with recipe() function

The first thing to do to create a recipe is to specify which variables we will be using as our outcome and predictors using the recipe() function. In terms of the metaphor of baking, we can think of this as listing our ingredients. Translating this to the recipes package, we use the recipe() function to assign roles to all the variables.

Let’s try the simplest recipe with no pre-processing steps: simply list the outcome and predictor variables.

We can do so in two ways:

  1. Using formula notation
  2. Assigning roles to each variable

Let’s look at the first way using formula notation, which looks like this:

outcome(s) ~ predictor(s)

If in the case of multiple predictors or a multivariate situation with two outcomes, use a plus sign:

outcome1 + outcome2 ~ predictor1 + predictor2

If we want to include all predictors we can use a period like so:

outcome_variable_name ~ .

Now with our data, we will start by making a recipe for our training data. If you recall, the continuous outcome variable is value (the average annual gravimetric monitor PM2.5 concentration in ug/m3). Our features (or predictor variables) are all the other variables except the monitor ID, which is an id variable.

The reason not to include the id variable is because this variable includes the county number and a number designating which particular monitor the values came from (of the monitors there are in that county). Since this number is arbitrary and the county information is also given in the data, and the fact that each monitor only has one value in the value variable, nothing is gained by including this variable and it may instead introduce noise. However, it is useful to keep this data to take a look at what is happening later. We will show you what to do in this case in just a bit.

In the simplest case, we might use all predictors like this:

Data Recipe

Inputs:

      role #variables
   outcome          1
 predictor         49

We see a recipe has been created with 1 outcome variable and 49 predictor variables (or features). Also, notice how we named the output of recipe(). The naming convention for recipe objects is *_rec or rec.

Now, let’s get back to the id variable. Instead of including it as a predictor variable, we could also use the update_role() function of the recipes package.

Data Recipe

Inputs:

        role #variables
 id variable          1
     outcome          1
   predictor         48

Click here learn more about the working with id variables

This option works well with the newer workflows package, however id variables are often dropped from analyses that do not use this newer package as they can make the process difficult with using the parsnip package alone due to the fact that new levels (or possible values) may be introduced with the testing data.

We could also specify the outcome and predictors in the same way as we just specified the id variable. Please see here for examples of other roles for variables. The role can be actually be any value.

The order is important here, as we first make all variables predictors and then override this role for the outcome and id variable. We will use the everything() function of the dplyr package to start with all of the variables in train_pm.

Data Recipe

Inputs:

        role #variables
 id variable          1
     outcome          1
   predictor         48

If we want to take a look at our formula from our recipe, we can do use the formula() function of the stats package.

value ~ fips + lat + lon + state + county + city + CMAQ + zcta + 
    zcta_area + zcta_pop + imp_a500 + imp_a1000 + imp_a5000 + 
    imp_a10000 + imp_a15000 + county_area + county_pop + log_dist_to_prisec + 
    log_pri_length_5000 + log_pri_length_10000 + log_pri_length_15000 + 
    log_pri_length_25000 + log_prisec_length_500 + log_prisec_length_1000 + 
    log_prisec_length_5000 + log_prisec_length_10000 + log_prisec_length_15000 + 
    log_prisec_length_25000 + log_nei_2008_pm25_sum_10000 + log_nei_2008_pm25_sum_15000 + 
    log_nei_2008_pm25_sum_25000 + log_nei_2008_pm10_sum_10000 + 
    log_nei_2008_pm10_sum_15000 + log_nei_2008_pm10_sum_25000 + 
    popdens_county + popdens_zcta + nohs + somehs + hs + somecollege + 
    associate + bachelor + grad + pov + hs_orless + urc2013 + 
    urc2006 + aod
<environment: 0x7f91f0c2e318>

We can also view our recipe in more detail using the base summary() function.

# A tibble: 50 x 4
   variable type    role        source  
   <chr>    <chr>   <chr>       <chr>   
 1 id       nominal id variable original
 2 value    numeric outcome     original
 3 fips     nominal predictor   original
 4 lat      numeric predictor   original
 5 lon      numeric predictor   original
 6 state    nominal predictor   original
 7 county   nominal predictor   original
 8 city     nominal predictor   original
 9 CMAQ     numeric predictor   original
10 zcta     nominal predictor   original
# … with 40 more rows

To summarize this step, we use the recipe() function to assign roles to all the variables:

Step 2: Specify the pre-processing steps with step*() functions

Next, we use the step*() functions from the recipe package to specify pre-processing steps.

This link and this link show the many options for recipe step functions.

There are step functions for a variety of purposes:

  1. Imputation – filling in missing values based on the existing data
  2. Transformation – changing all values of a variable in the same way, typically to make it more normal or easier to interpret
  3. Discretization – converting continuous values into discrete or nominal values - binning for example to reduce the number of possible levels (However this is generally not advisable!)
  4. Encoding / Creating Dummy Variables – creating a numeric code for categorical variables (More on Dummy Variables and one hot encoding)
  5. Data type conversions – which means changing from integer to factor or numeric to date etc.
  6. Interaction term addition to the model – which means that we would be modeling for predictors that would influence the capacity of each other to predict the outcome
  7. Normalization – centering and scaling the data to a similar range of values
  8. Dimensionality Reduction/ Signal Extraction – reducing the space of features or predictors to a smaller set of variables that capture the variation or signal in the original variables (ex. Principal Component Analysis and Independent Component Analysis)
  9. Filtering – filtering options for removing variables (ex. remove variables that are highly correlated to others or remove variables with very little variance and therefore likely little predictive capacity)
  10. Row operations – performing functions on the values within the rows (ex. rearranging, filtering, imputing)
  11. Checking functions – Sanity checks to look for missing values, to look at the variable classes etc.

All of the step functions look like step_*() with the * replaced with a name, except for the check functions which look like check_*().

There are several ways to select what variables to apply steps to:

  1. Using tidyselect methods: contains(), matches(), starts_with(), ends_with(), everything(), num_range()
  2. Using the type: all_nominal(), all_numeric() , has_type()
  3. Using the role: all_predictors(), all_outcomes(), has_role()
  4. Using the name - use the actual name of the variable/variables of interest

Let’s try adding some steps to our recipe.

We might want to potentially one hot encode some of our categorical variables so that they can be used with certain algorithms.

We can do this with the step_dummy() function and the one_hot = TRUE argument. One hot encoding means that we do not simply encode our categorical variables numerically, as our numeric assignments can be interpreted by algorithms as having a particular rank or order. Instead, binary variables made of 1s and 0s are used to arbitrarily assign a numeric value that has no apparent order.

Data Recipe

Inputs:

        role #variables
 id variable          1
     outcome          1
   predictor         48

Operations:

Dummy variables from state, county, city, zcta

Our fips variable includes a numeric code for state and county - and therefore is essentially a proxy for county. Since we already have county, we will just use it and keep the fips ID as another ID variable.

We can remove the fips variable from the predictors using update_role() to make sure that the role is no longer "predictor". We can make the role anything we want actually, so we will keep it something identifiable.

Data Recipe

Inputs:

        role #variables
   county id          1
 id variable          1
     outcome          1
   predictor         47

We might also want to remove variables that appear to be redundant and are highly correlated with others, as we know from our exploratory data analysis that many of our variables are correlated with one another. We can do this using the step_corr() function.

We don’t want to remove some of our variables, like the CMAQ and aod variables, we can specify this using the - sign before the names of these variables like so:

Data Recipe

Inputs:

        role #variables
 id variable          1
     outcome          1
   predictor         48

Operations:

Correlation filter on all_predictors, -, CMAQ, -, aod

It is also a good idea to remove variables with near-zero variance, which can be done with the step_nzv() function.

Variables have low variance if all the values are very similar, the values are very sparse, or if they are highly imbalanced. Again we don’t want to remove our CMAQ and aod variables.

Data Recipe

Inputs:

        role #variables
 id variable          1
     outcome          1
   predictor         48

Operations:

Sparse, unbalanced variable filter on all_predictors, -, CMAQ, -, aod

Click here to learn about examples where you might have near-zero variance variables

  1. Similar Values - If the population density was nearly the same for every zcta that contained a monitor, then knowing the population density near our monitor would contribute little to our model in assisting us to predict monitor air pollution values.
  2. Sparse Data - If all of the monitors were in locations where the populations did not attend graduate school, then these values would mostly be zero, again this would do very little to help us distinguish our air pollution monitors.When many of the values are zero this is also called sparse data.
  3. Imbalanced Data If nearly all of the monitors were located in one particular state, and all the others only had one monitor each, then the real predictive value would simply be in knowing if a monitor is located in that particular state or not. In this case we don’t want to remove our variable, we just want to simplify it.

See this blog post about why removing near-zero variance variables isn’t always a good idea if we think that a variable might be especially informative.

Let’s put all this together now.

Remember: it is important to add the steps to the recipe in an order that makes sense just like with a cooking recipe.

First, we are going to create numeric values for our categorical variables, then we will look at correlation and near-zero variance. Again, we do not want to remove the CMAQ and aod variables, so we can make sure they are kept in the model by excluding them from those steps. If we specifically wanted to remove a predictor we could use step_rm().

Data Recipe

Inputs:

        role #variables
   county id          1
 id variable          1
     outcome          1
   predictor         47

Operations:

Dummy variables from state, county, city, zcta
Correlation filter on all_predictors, -, CMAQ, -, aod
Sparse, unbalanced variable filter on all_predictors, -, CMAQ, -, aod

Running the pre-processing

Step 1: Update the recipe with training data using prep()

The next major function of the recipes package is prep(). This function updates the recipe object based on the training data. It estimates parameters (estimating the required quantities and statistics required by the steps for the variables) for pre-processing and updates the variables roles, as some of the predictors may be removed, this allows the recipe to be ready to use on other data sets. It does not necessarily actually execute the pre-processing itself, however we will specify in argument for it to do this so that we can take a look at the pre-processed data.

There are some important arguments to know about:

  1. training - you must supply a training data set to estimate parameters for pre-processing operations (recipe steps) - this may already be included in your recipe - as is the case for us
  2. fresh - if fresh=TRUE, - will retrain and estimate parameters for any previous steps that were already prepped if you add more steps to the recipe
  3. verbose - if verbose=TRUE, shows the progress as the steps are evaluated and the size of the pre-processed training set
  4. retain - if retain=TRUE, then the pre-processed training set will be saved within the recipe (as template). This is good if you are likely to add more steps and do not want to rerun the prep() on the previous steps. However this can make the recipe size large. This is necessary if you want to actually look at the pre-processed data.

Let’s try out the prep() function:

oper 1 step dummy [training] 
oper 2 step corr [training] 
oper 3 step nzv [training] 
The retained training set is ~ 0.26 Mb  in memory.
[1] "var_info"       "term_info"      "steps"          "template"      
[5] "levels"         "retained"       "tr_info"        "orig_lvls"     
[9] "last_term_info"

There are also lots of useful things to checkout in the output of prep(). You can see:

  1. the steps that were run
  2. the original variable info (var_info)
  3. the updated variable info after pre-processing (term_info)
  4. the new levels of the variables
  5. the original levels of the variables (orig_lvls)
  6. info about the training data set size and completeness (tr_info)

Note: You may see the prep.recipe() function in material that you read about the recipes package. This is referring to the prep() function of the recipes package.

Step 2: Extract pre-processed training data using juice()

Since we retained our pre-processed training data (i.e. prep(retain=TRUE)), we can take a look at it like by using the juice() function of the recipes package like this:

Let’s juice!

Rows: 584
Columns: 36
$ id                          <fct> 1003.001, 1027.0001, 1033.1002, 1055.001,…
$ value                       <dbl> 9.597647, 10.800000, 11.212174, 12.375394…
$ fips                        <fct> 1003, 1027, 1033, 1055, 1069, 1073, 1073,…
$ lat                         <dbl> 30.49800, 33.28126, 34.75878, 33.99375, 3…
$ lon                         <dbl> -87.88141, -85.80218, -87.65056, -85.9910…
$ CMAQ                        <dbl> 8.098836, 9.766208, 9.402679, 9.241744, 9…
$ zcta_area                   <dbl> 190980522, 374132430, 16716984, 154069359…
$ zcta_pop                    <dbl> 27829, 5103, 9042, 20045, 30217, 9010, 16…
$ imp_a500                    <dbl> 0.01730104, 1.96972318, 19.17301038, 16.4…
$ imp_a15000                  <dbl> 1.4386207, 0.3359198, 5.2472094, 5.161210…
$ county_area                 <dbl> 4117521611, 1564252280, 1534877333, 13856…
$ county_pop                  <dbl> 182265, 13932, 54428, 104430, 101547, 658…
$ log_dist_to_prisec          <dbl> 4.648181, 7.219907, 5.760131, 5.261457, 7…
$ log_pri_length_5000         <dbl> 8.517193, 8.517193, 8.517193, 9.066563, 8…
$ log_pri_length_25000        <dbl> 11.32735, 10.12663, 10.15769, 12.01356, 1…
$ log_prisec_length_500       <dbl> 7.295356, 6.214608, 8.611945, 8.740680, 6…
$ log_prisec_length_1000      <dbl> 8.195119, 7.600902, 9.735569, 9.627898, 7…
$ log_prisec_length_5000      <dbl> 10.815042, 10.170878, 11.770407, 11.72888…
$ log_prisec_length_10000     <dbl> 11.886803, 11.405543, 12.840663, 12.76827…
$ log_nei_2008_pm10_sum_15000 <dbl> 2.26783411, 3.31111648, 6.70127741, 4.462…
$ log_nei_2008_pm10_sum_25000 <dbl> 5.628728, 3.311116, 7.148858, 4.678311, 3…
$ popdens_county              <dbl> 44.265706, 8.906492, 35.460814, 75.367038…
$ popdens_zcta                <dbl> 145.7164307, 13.6395554, 540.8870404, 130…
$ nohs                        <dbl> 3.3, 11.6, 7.3, 4.3, 5.8, 7.1, 2.7, 11.1,…
$ somehs                      <dbl> 4.9, 19.1, 15.8, 13.3, 11.6, 17.1, 6.6, 1…
$ hs                          <dbl> 25.1, 33.9, 30.6, 27.8, 29.8, 37.2, 30.7,…
$ somecollege                 <dbl> 19.7, 18.8, 20.9, 29.2, 21.4, 23.5, 25.7,…
$ associate                   <dbl> 8.2, 8.0, 7.6, 10.1, 7.9, 7.3, 8.0, 4.1, …
$ bachelor                    <dbl> 25.3, 5.5, 12.7, 10.0, 13.7, 5.9, 17.6, 7…
$ grad                        <dbl> 13.5, 3.1, 5.1, 5.4, 9.8, 2.0, 8.7, 2.9, …
$ pov                         <dbl> 6.1, 19.5, 19.0, 8.8, 15.6, 25.5, 7.3, 8.…
$ hs_orless                   <dbl> 33.3, 64.6, 53.7, 45.4, 47.2, 61.4, 40.0,…
$ urc2013                     <dbl> 4, 6, 4, 4, 4, 1, 1, 1, 1, 1, 2, 3, 3, 3,…
$ aod                         <dbl> 37.363636, 34.818182, 36.000000, 43.41666…
$ state_California            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
$ city_Not.in.a.city          <dbl> 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0,…

For easy comparison sake - here is our original data:

Rows: 876
Columns: 50
$ id                          <fct> 1003.001, 1027.0001, 1033.1002, 1049.1003…
$ value                       <dbl> 9.597647, 10.800000, 11.212174, 11.659091…
$ fips                        <fct> 1003, 1027, 1033, 1049, 1055, 1069, 1073,…
$ lat                         <dbl> 30.49800, 33.28126, 34.75878, 34.28763, 3…
$ lon                         <dbl> -87.88141, -85.80218, -87.65056, -85.9683…
$ state                       <chr> "Alabama", "Alabama", "Alabama", "Alabama…
$ county                      <chr> "Baldwin", "Clay", "Colbert", "DeKalb", "…
$ city                        <chr> "Fairhope", "Ashland", "Muscle Shoals", "…
$ CMAQ                        <dbl> 8.098836, 9.766208, 9.402679, 8.534772, 9…
$ zcta                        <fct> 36532, 36251, 35660, 35962, 35901, 36303,…
$ zcta_area                   <dbl> 190980522, 374132430, 16716984, 203836235…
$ zcta_pop                    <dbl> 27829, 5103, 9042, 8300, 20045, 30217, 90…
$ imp_a500                    <dbl> 0.01730104, 1.96972318, 19.17301038, 5.78…
$ imp_a1000                   <dbl> 1.4096021, 0.8531574, 11.1448962, 3.86764…
$ imp_a5000                   <dbl> 3.3360118, 0.9851479, 15.1786154, 1.23114…
$ imp_a10000                  <dbl> 1.9879187, 0.5208189, 9.7253870, 1.031646…
$ imp_a15000                  <dbl> 1.4386207, 0.3359198, 5.2472094, 0.973044…
$ county_area                 <dbl> 4117521611, 1564252280, 1534877333, 20126…
$ county_pop                  <dbl> 182265, 13932, 54428, 71109, 104430, 1015…
$ log_dist_to_prisec          <dbl> 4.648181, 7.219907, 5.760131, 3.721489, 5…
$ log_pri_length_5000         <dbl> 8.517193, 8.517193, 8.517193, 8.517193, 9…
$ log_pri_length_10000        <dbl> 9.210340, 9.210340, 9.274303, 10.409411, …
$ log_pri_length_15000        <dbl> 9.630228, 9.615805, 9.658899, 11.173626, …
$ log_pri_length_25000        <dbl> 11.32735, 10.12663, 10.15769, 11.90959, 1…
$ log_prisec_length_500       <dbl> 7.295356, 6.214608, 8.611945, 7.310155, 8…
$ log_prisec_length_1000      <dbl> 8.195119, 7.600902, 9.735569, 8.585843, 9…
$ log_prisec_length_5000      <dbl> 10.815042, 10.170878, 11.770407, 10.21420…
$ log_prisec_length_10000     <dbl> 11.88680, 11.40554, 12.84066, 11.50894, 1…
$ log_prisec_length_15000     <dbl> 12.205723, 12.042963, 13.282656, 12.35366…
$ log_prisec_length_25000     <dbl> 13.41395, 12.79980, 13.79973, 13.55979, 1…
$ log_nei_2008_pm25_sum_10000 <dbl> 0.318035438, 3.218632928, 6.573127301, 0.…
$ log_nei_2008_pm25_sum_15000 <dbl> 1.967358961, 3.218632928, 6.581917457, 3.…
$ log_nei_2008_pm25_sum_25000 <dbl> 5.067308, 3.218633, 6.875900, 4.887665, 4…
$ log_nei_2008_pm10_sum_10000 <dbl> 1.35588511, 3.31111648, 6.69187313, 0.000…
$ log_nei_2008_pm10_sum_15000 <dbl> 2.26783411, 3.31111648, 6.70127741, 3.350…
$ log_nei_2008_pm10_sum_25000 <dbl> 5.628728, 3.311116, 7.148858, 5.171920, 4…
$ popdens_county              <dbl> 44.265706, 8.906492, 35.460814, 35.330814…
$ popdens_zcta                <dbl> 145.716431, 13.639555, 540.887040, 40.718…
$ nohs                        <dbl> 3.3, 11.6, 7.3, 14.3, 4.3, 5.8, 7.1, 2.7,…
$ somehs                      <dbl> 4.9, 19.1, 15.8, 16.7, 13.3, 11.6, 17.1, …
$ hs                          <dbl> 25.1, 33.9, 30.6, 35.0, 27.8, 29.8, 37.2,…
$ somecollege                 <dbl> 19.7, 18.8, 20.9, 14.9, 29.2, 21.4, 23.5,…
$ associate                   <dbl> 8.2, 8.0, 7.6, 5.5, 10.1, 7.9, 7.3, 8.0, …
$ bachelor                    <dbl> 25.3, 5.5, 12.7, 7.9, 10.0, 13.7, 5.9, 17…
$ grad                        <dbl> 13.5, 3.1, 5.1, 5.8, 5.4, 9.8, 2.0, 8.7, …
$ pov                         <dbl> 6.1, 19.5, 19.0, 13.8, 8.8, 15.6, 25.5, 7…
$ hs_orless                   <dbl> 33.3, 64.6, 53.7, 66.0, 45.4, 47.2, 61.4,…
$ urc2013                     <dbl> 4, 6, 4, 6, 4, 4, 1, 1, 1, 1, 1, 1, 1, 2,…
$ urc2006                     <dbl> 5, 6, 4, 5, 4, 4, 1, 1, 1, 1, 1, 1, 1, 2,…
$ aod                         <dbl> 37.36364, 34.81818, 36.00000, 33.08333, 4…

Notice how we only have 36 variables now instead of 50! Two of these are our ID variables (fips and the actual monitor ID (id)) and one is our outcome (value). Thus we only have 33 predictors now. We can also see that variables that we no longer have any categorical variables. Variables like state are gone and only state_California remains as it was the only state identity to have nonzero variance. We can also see that there were more monitors listed as "Not in a city" than any city.

We can see that California had the largest number of monitors compared to the other states.

Scroll through the output:

# A tibble: 49 x 2
   state                    n
   <chr>                <int>
 1 Alabama                 24
 2 Arizona                 17
 3 Arkansas                16
 4 California              85
 5 Colorado                15
 6 Connecticut             14
 7 Delaware                 7
 8 District Of Columbia     3
 9 Florida                 29
10 Georgia                 28
11 Idaho                    7
12 Illinois                38
13 Indiana                 36
14 Iowa                    20
15 Kansas                  10
16 Kentucky                22
17 Louisiana               17
18 Maine                    1
19 Maryland                15
20 Massachusetts           16
21 Michigan                30
22 Minnesota               17
23 Mississippi             12
24 Missouri                13
25 Montana                 16
26 Nebraska                 7
27 Nevada                   4
28 New Hampshire            7
29 New Jersey              23
30 New Mexico              10
31 New York                24
32 North Carolina          35
33 North Dakota             4
34 Ohio                    44
35 Oklahoma                10
36 Oregon                  17
37 Pennsylvania            32
38 Rhode Island             5
39 South Carolina          14
40 South Dakota             9
41 Tennessee                3
42 Texas                   27
43 Utah                    14
44 Vermont                  4
45 Virginia                20
46 Washington               8
47 West Virginia           14
48 Wisconsin               21
49 Wyoming                 12

Scroll through the output:

# A tibble: 607 x 2
    city                                                 n
    <chr>                                            <int>
  1 Aberdeen                                             1
  2 Akron                                                2
  3 Albany                                               3
  4 Albuquerque                                          2
  5 Alexandria                                           1
  6 Allen Park                                           1
  7 Altamont                                             1
  8 Alton                                                1
  9 Amarillo                                             1
 10 Anadarko                                             1
 11 Anaheim                                              1
 12 Anderson                                             1
 13 Annandale                                            1
 14 Apache Junction                                      1
 15 Apple Valley                                         1
 16 Appleton                                             1
 17 Arden-Arcade                                         1
 18 Arlington                                            1
 19 Arnold                                               1
 20 Asheville                                            1
 21 Ashland                                              2
 22 Atascadero                                           1
 23 Athens-Clarke County (Remainder)                     1
 24 Atlanta                                              2
 25 Atlantic City                                        1
 26 Augusta-Richmond County (Remainder)                  2
 27 Aurora                                               1
 28 Austin                                               1
 29 Azusa                                                1
 30 Bakersfield                                          3
 31 Baltimore                                            5
 32 Batavia                                              1
 33 Baton Rouge                                          1
 34 Bay City                                             1
 35 Bayport                                              1
 36 Baytown                                              1
 37 Beaver Falls                                         1
 38 Beckley                                              1
 39 Belle Glade                                          1
 40 Bellevue                                             1
 41 Beltsville                                           1
 42 Bend                                                 1
 43 Bennington                                           1
 44 Bensley                                              1
 45 Big Bear City                                        1
 46 Billings                                             1
 47 Birmingham                                           2
 48 Bismarck                                             1
 49 Bladensburg                                          1
 50 Blair                                                1
 51 Blue Ash                                             1
 52 Blue Island                                          1
 53 Boise (corporate name Boise City)                    1
 54 Boone                                                1
 55 Boston                                               4
 56 Boulder                                              1
 57 Boulevard                                            1
 58 Bountiful                                            1
 59 Braidwood                                            1
 60 Brawley                                              1
 61 Bridgeport                                           1
 62 Brigham City                                         1
 63 Bristol                                              2
 64 Brockton                                             1
 65 Brook Park                                           1
 66 Brookings                                            1
 67 Brunswick                                            1
 68 Bryson City (RR name Bryson)                         1
 69 Buffalo                                              1
 70 Burbank                                              1
 71 Burlington                                           2
 72 Burns                                                1
 73 Butte-Silver Bow (Remainder)                         1
 74 Calexico                                             1
 75 Camden                                               1
 76 Candor                                               1
 77 Canton                                               2
 78 Carlisle                                             1
 79 Carlstadt                                            3
 80 Cary                                                 1
 81 Casa Grande                                          1
 82 Cedar Rapids                                         2
 83 Cedarhurst                                           1
 84 Central Point                                        1
 85 Chalmette                                            1
 86 Champaign                                            1
 87 Chapel Hill                                          1
 88 Charleroi                                            1
 89 Charleston                                           2
 90 Charlotte                                            2
 91 Chattanooga                                          1
 92 Chelmsford (Chelmsford Center)                       1
 93 Chester                                              2
 94 Cheyenne                                             1
 95 Chicago                                              5
 96 Chickasaw                                            1
 97 Chicopee                                             1
 98 Childersburg                                         1
 99 Chula Vista                                          1
100 Cicero                                               1
101 Cincinnati                                           3
102 Clairton                                             1
103 Claremont                                            1
104 Clarion                                              1
105 Clarksburg                                           1
106 Clearwater                                           1
107 Cleveland                                            6
108 Clinton                                              2
109 Clive                                                1
110 Clovis                                               1
111 Cockeysville                                         1
112 Cody                                                 1
113 Coloma                                               1
114 Colorado Springs                                     1
115 Columbia                                             1
116 Columbia Falls                                       1
117 Columbus                                             4
118 Columbus (Remainder)                                 3
119 Colusa                                               1
120 Commerce City                                        1
121 Concord                                              1
122 Conway                                               1
123 Corcoran                                             1
124 Cornwall                                             1
125 Corpus Christi                                       2
126 Cottage Grove                                        1
127 Cottonwood West                                      1
128 Council Bluffs                                       1
129 Covington                                            1
130 Crossett                                             1
131 Crossville                                           1
132 Dale                                                 1
133 Dallas                                               3
134 Danbury                                              1
135 Darrington                                           1
136 Davenport                                            3
137 Davie                                                1
138 Dayton                                               1
139 Dearborn                                             1
140 Decatur                                              2
141 Delray Beach                                         1
142 Dentsville (Dents)                                   1
143 Denver                                               3
144 Des Moines                                           1
145 Des Plaines                                          1
146 Detroit                                              5
147 Doraville                                            1
148 Dothan                                               1
149 Douglas                                              1
150 Dover                                                1
151 Duluth                                               2
152 Durham                                               1
153 East Chicago                                         1
154 East Farmingdale                                     1
155 East Hartford                                        2
156 East Highland Park                                   1
157 East Providence                                      1
158 East Ridge                                           1
159 East Saint Louis                                     1
160 East Syracuse                                        1
161 Edgewood                                             1
162 El Cajon                                             1
163 El Centro                                            1
164 El Dorado                                            1
165 El Paso                                              3
166 Elgin                                                1
167 Elizabeth                                            2
168 Elizabethtown                                        1
169 Elkhart                                              1
170 Emmetsburg                                           1
171 Erie                                                 1
172 Escondido                                            1
173 Essex                                                1
174 Eugene                                               2
175 Eureka                                               2
176 Evansville                                           3
177 Fairfield                                            1
178 Fairhope                                             1
179 Fairmont                                             1
180 Fall River                                           1
181 Farmington                                           1
182 Farrell                                              1
183 Fayetteville                                         1
184 Ferry Pass                                           1
185 Flagstaff                                            1
186 Flint                                                1
187 Follansbee                                           1
188 Fontana                                              1
189 Forest Park                                          1
190 Fort Collins                                         1
191 Fort Defiance                                        1
192 Fort Lee                                             1
193 Fort Myers                                           1
194 Fort Pierce                                          1
195 Fort Smith                                           1
196 Fort Wayne                                           1
197 Fort Worth                                           2
198 Frankfort                                            1
199 Franklin                                             1
200 Freemansburg                                         1
201 Fremont                                              1
202 Fresno                                               2
203 Gadsden                                              1
204 Gainesville                                          3
205 Galloway (Township of)                               1
206 Garden City                                          1
207 Gary                                                 3
208 Gastonia                                             1
209 Gilroy                                               1
210 Glen Burnie                                          1
211 Goldsboro                                            1
212 Gordon                                               1
213 Grand Island                                         1
214 Grand Junction                                       1
215 Grand Rapids                                         2
216 Granite City                                         2
217 Grants Pass                                          1
218 Grass Valley                                         1
219 Great Falls                                          1
220 Greater Upper Marlboro                               1
221 Greeley                                              1
222 Green Bay                                            1
223 Greensboro                                           1
224 Greensburg                                           1
225 Greenville                                           3
226 Greenwich (Township of)                              1
227 Grenada                                              1
228 Griffith                                             1
229 Groveton                                             1
230 Gulfport                                             1
231 Hamilton                                             1
232 Hammond                                              3
233 Hampton                                              1
234 Harrison Township                                    1
235 Harrisville                                          1
236 Hattiesburg                                          1
237 Haverhill                                            1
238 Helena                                               2
239 Helena Valley West Central                           1
240 Hernando                                             1
241 Hickory                                              1
242 Highland                                             1
243 Highland Heights                                     1
244 Hillsboro                                            1
245 Hobbs                                                1
246 Holland                                              1
247 Hollister                                            1
248 Hollywood                                            1
249 Homestead                                            1
250 Hoover                                               1
251 Hopewell (Township of)                               1
252 Hot Springs (Hot Springs National Park)              1
253 Houston                                              2
254 Huntington                                           1
255 Huntsville                                           1
256 Indianapolis                                         1
257 Indianapolis (Remainder)                             4
258 Indio                                                1
259 Iowa City                                            1
260 Ironton                                              1
261 Jackson                                              2
262 Jacksonville                                         2
263 Jamesville                                           1
264 Jasper                                               3
265 Jean                                                 1
266 Jeffersonville                                       1
267 Jenison                                              1
268 Jersey City                                          1
269 Jerseyville                                          1
270 Johnstown                                            1
271 Joliet                                               1
272 Kalamazoo                                            1
273 Kalispell                                            1
274 Kansas City                                          3
275 Keeler                                               1
276 Keene                                                1
277 Kenansville                                          1
278 Kenner                                               1
279 Kennesaw                                             1
280 Keokuk                                               1
281 Kinston                                              1
282 Kokomo                                               1
283 La Crosse                                            1
284 La Grande                                            1
285 Lackawanna                                           1
286 Laconia                                              1
287 Ladue                                                1
288 Lafayette                                            3
289 Lake Charles                                         1
290 Lakeland                                             1
291 Lakeport                                             1
292 Lakeview                                             1
293 Lancaster                                            2
294 Lander                                               1
295 Lansing                                              1
296 Las Cruces                                           1
297 Las Vegas                                            1
298 Laurel                                               1
299 Lawrence                                             1
300 Leander                                              1
301 Lebanon                                              2
302 Leeds                                                1
303 Lexington                                            1
304 Lexington-Fayette (corporate name for Lexington)     2
305 Libby                                                1
306 Liberty                                              1
307 Lincoln                                              1
308 Lindon                                               1
309 Little Rock                                          2
310 Littleton                                            1
311 Live Oak                                             1
312 Livermore                                            1
313 Livonia                                              1
314 Logan                                                1
315 Long Beach                                           2
316 Longmont                                             1
317 Los Angeles                                          1
318 Louisville                                           4
319 Luna Pier                                            1
320 Lynchburg                                            1
321 Lynn                                                 1
322 Lynwood                                              1
323 Macon                                                2
324 Madison                                              1
325 Magna                                                1
326 Mamaroneck                                           1
327 Manistee                                             1
328 Marble City Community                                1
329 Maricopa                                             1
330 Marion                                               2
331 Marrero                                              1
332 Martinsburg                                          1
333 Marysville                                           1
334 McAlester                                            1
335 McCook                                               1
336 McDonald                                             1
337 McLean                                               1
338 Medford                                              1
339 Melbourne                                            1
340 Mena                                                 1
341 Merced                                               1
342 Meridian                                             1
343 Mesa                                                 1
344 Miami                                                2
345 Michigan City                                        1
346 Middlesborough (corporate name for Middlesboro)      1
347 Middletown                                           2
348 Midlothian                                           1
349 Milwaukee                                            5
350 Mingo Junction                                       1
351 Minneapolis                                          2
352 Mira Loma                                            1
353 Mission                                              1
354 Mission Viejo                                        1
355 Missoula                                             1
356 Modesto                                              1
357 Mojave                                               1
358 Monroe                                               1
359 Montgomery                                           1
360 Morgantown                                           1
361 Morristown                                           1
362 Moundsville                                          1
363 Muncie                                               1
364 Muscatine                                            1
365 Muscle Shoals                                        1
366 Muskegon                                             1
367 Muskogee                                             1
368 Naperville                                           1
369 Nashua                                               1
370 Natchez                                              1
371 New Albany                                           1
372 New Haven                                            5
373 New Paris                                            1
374 New York                                             9
375 Newark                                               2
376 Newburgh                                             1
377 Newburgh Heights                                     1
378 Newport                                              1
379 Niagara Falls                                        1
380 Nogales                                              1
381 Norfolk                                              1
382 Normal                                               1
383 Norristown                                           1
384 North Braddock                                       1
385 North Brunswick Township                             1
386 North Charleston                                     1
387 North Las Vegas                                      1
388 North Little Rock                                    1
389 Northbrook                                           1
390 Norwalk                                              1
391 Norwich                                              1
392 Norwood                                              1
393 Not in a city                                      103
394 Oak Park                                             1
395 Oakland                                              2
396 Oakridge                                             1
397 Odessa                                               1
398 Ogden                                                1
399 Ogden Dunes (Wickliffe)                              1
400 Oglesby                                              1
401 Oklahoma City                                        2
402 Omaha                                                2
403 Onamia                                               1
404 Ontario                                              1
405 Orlando                                              1
406 Overland Park                                        1
407 Paducah                                              1
408 Painesville                                          1
409 Palm Springs                                         1
410 Palm Springs North                                   1
411 Panama City                                          1
412 Pasadena                                             1
413 Pascagoula                                           1
414 Paterson                                             1
415 Pawtucket                                            1
416 Peach Springs                                        1
417 Pelham                                               1
418 Pendleton                                            1
419 Pennsauken (Pensauken)                               1
420 Peoria                                               1
421 Phenix City                                          1
422 Philadelphia                                         5
423 Phillipsburg                                         1
424 Phoenix                                              3
425 Pico Rivera                                          1
426 Pikeville                                            1
427 Pinedale                                             1
428 Pinehurst (Pine Creek)                               1
429 Pinson                                               1
430 Piru                                                 1
431 Pittsboro                                            1
432 Pittsburgh                                           1
433 Pittsfield                                           1
434 Platteville                                          1
435 Pleasant Prairie                                     1
436 Pompano Beach                                        1
437 Port Arthur                                          1
438 Port Huron                                           1
439 Portland                                             2
440 Portola                                              1
441 Portsmouth                                           2
442 Potosi                                               1
443 Potsdam                                              1
444 Powder Springs                                       1
445 Prescott Valley                                      1
446 Presque Isle                                         1
447 Providence                                           2
448 Provo                                                1
449 Pryor (corporate name Pryor Creek)                   1
450 Pueblo                                               1
451 Quincy                                               2
452 Rahway                                               1
453 Raleigh                                              1
454 Rapid City                                           2
455 Ravenna                                              1
456 Redding                                              1
457 Redwood City                                         1
458 Reno                                                 1
459 Reseda                                               1
460 Richfield                                            1
461 Richmond                                             1
462 Ridge Wood Heights                                   1
463 Ridgecrest                                           1
464 Rio Rancho Estates                                   1
465 Riverside                                            1
466 Roanoke                                              2
467 Rochester                                            2
468 Rock Island Arsenal (U.S. Army)                      1
469 Rock Springs                                         1
470 Rockford                                             1
471 Rockwell                                             1
472 Rocky Mount                                          1
473 Rome                                                 1
474 Roseville                                            1
475 Roswell                                              1
476 Roxborough Park                                      1
477 Royal Palm Beach                                     1
478 Rubidoux                                             1
479 Russellville                                         1
480 Rutland                                              1
481 Sacramento                                           2
482 Saint Petersburg                                     1
483 Salinas                                              1
484 Salt Lake City                                       2
485 San Andreas                                          1
486 San Antonio                                          2
487 San Bernardino                                       1
488 San Diego                                            2
489 San Francisco                                        1
490 San Jose                                             1
491 San Luis Obispo                                      1
492 Sandersville                                         1
493 Sanford                                              1
494 Santa Barbara                                        1
495 Santa Fe                                             1
496 Santa Maria                                          1
497 Santa Rosa                                           1
498 Sault Ste. Marie                                     2
499 Savannah                                             1
500 Schiller Park                                        1
501 Scottsbluff                                          1
502 Scottsdale                                           1
503 Scranton                                             1
504 Seaford                                              1
505 Searcy                                               1
506 Seattle                                              2
507 Seeley Lake                                          1
508 Seven Oaks                                           1
509 Shakopee                                             1
510 Sharonville                                          1
511 Shasta Lake                                          1
512 Sheffield                                            1
513 Shepherdsville                                       1
514 Sheridan                                             2
515 Shreveport                                           1
516 Silver City                                          1
517 Simi Valley                                          1
518 Sioux City                                           1
519 Sioux Falls                                          2
520 Soddy-Daisy                                          1
521 South Bend                                           2
522 South Charleston                                     1
523 South Padre Island                                   1
524 Spanish Fork                                         1
525 Spokane                                              1
526 Springdale                                           1
527 Springfield                                          5
528 Spruce Pine                                          1
529 St. Bernard                                          1
530 St. Cloud                                            1
531 St. Joseph                                           1
532 St. Louis                                            4
533 St. Louis Park                                       1
534 St. Paul                                             3
535 State College                                        1
536 Ste. Genevieve                                       1
537 Steubenville                                         1
538 Stockton                                             1
539 Stuttgart                                            1
540 Summit                                               1
541 Suncook                                              1
542 Swansea                                              1
543 Tacoma                                               1
544 Tallahassee                                          1
545 Tampa                                                1
546 Taylors                                              1
547 Tecumseh                                             1
548 Terre Haute                                          2
549 Texarkana                                            1
550 Theodore                                             1
551 Thomaston                                            1
552 Thompson Falls                                       1
553 Thousand Oaks                                        1
554 Toledo                                               3
555 Toms River                                           1
556 Tooele                                               1
557 Topeka                                               1
558 Trenton                                              1
559 Truckee                                              1
560 Tucson                                               2
561 Tulsa                                                2
562 Tupelo                                               1
563 Tuscaloosa                                           1
564 Ukiah                                                1
565 Underhill (Town of)                                  1
566 Union City                                           1
567 Valdosta                                             1
568 Vallejo                                              1
569 Valrico                                              1
570 Vancouver                                            1
571 Victorville                                          1
572 Vienna                                               1
573 Vinton                                               1
574 Virginia                                             1
575 Virginia Beach                                       1
576 Visalia                                              1
577 Warner Robins                                        1
578 Warren                                               1
579 Washington                                           4
580 Waterbury                                            1
581 Waterloo                                             1
582 Watertown                                            1
583 Waukesha                                             1
584 Waynesville                                          1
585 Weirton                                              2
586 West Orange                                          1
587 West Yellowstone                                     1
588 Westfield                                            1
589 Westport                                             1
590 Wheeling                                             1
591 Whitefish                                            1
592 Wichita                                              3
593 Wilmington                                           1
594 Winston-Salem                                        2
595 Winter Park                                          1
596 Wood River                                           1
597 Woodland                                             1
598 Worcester                                            2
599 Wyandotte                                            1
600 Yakima                                               1
601 Yellow Springs                                       1
602 York                                                 1
603 Youngstown                                           2
604 Ypsilanti                                            1
605 Yuba City                                            1
606 Yuma                                                 1
607 Zion                                                 1

Note: Recall that you must specify retain = TRUE argument of the prep() function to use juice().

Step 3: Extract pre-processed testing data using bake()

According to the tidymodels documentation:

bake() takes a trained recipe and applies the operations to a data set to create a design matrix. For example: it applies the centering to new data sets using these means used to create the recipe

Therefore, if you wanted to look at the pre-processed testing data you would use the bake() function of the recipes package. (You generally want to leave your testing data alone, but it is good to look for issues like the introduction of NA values).

Let’s bake!

Rows: 292
Columns: 36
$ id                          <fct> 1049.1003, 1073.101, 1073.2006, 1089.0014…
$ value                       <dbl> 11.659091, 13.114545, 12.228125, 12.23294…
$ fips                        <fct> 1049, 1073, 1073, 1089, 1103, 1121, 4013,…
$ lat                         <dbl> 34.28763, 33.54528, 33.38639, 34.68767, 3…
$ lon                         <dbl> -85.96830, -86.54917, -86.81667, -86.5863…
$ CMAQ                        <dbl> 8.534772, 9.303766, 10.235612, 9.343611, …
$ zcta_area                   <dbl> 203836235, 148994881, 56063756, 46963946,…
$ zcta_pop                    <dbl> 8300, 14212, 32390, 21297, 30545, 7713, 5…
$ imp_a500                    <dbl> 5.78200692, 0.06055363, 42.42820069, 23.2…
$ imp_a15000                  <dbl> 0.9730444, 2.9956557, 12.7487614, 10.3555…
$ county_area                 <dbl> 2012662359, 2878192209, 2878192209, 20761…
$ county_pop                  <dbl> 71109, 658466, 658466, 334811, 119490, 82…
$ log_dist_to_prisec          <dbl> 3.721489, 7.301545, 4.721755, 4.659519, 6…
$ log_pri_length_5000         <dbl> 8.517193, 9.683336, 10.737240, 8.517193, …
$ log_pri_length_25000        <dbl> 11.90959, 12.53777, 12.99669, 11.47391, 1…
$ log_prisec_length_500       <dbl> 7.310155, 6.214608, 7.528913, 8.760549, 6…
$ log_prisec_length_1000      <dbl> 8.585843, 7.600902, 9.342290, 9.543183, 8…
$ log_prisec_length_5000      <dbl> 10.214200, 11.262645, 11.713190, 11.48606…
$ log_prisec_length_10000     <dbl> 11.50894, 12.14101, 12.53899, 12.68440, 1…
$ log_nei_2008_pm10_sum_15000 <dbl> 3.3500444, 6.6241114, 5.8268686, 3.861625…
$ log_nei_2008_pm10_sum_25000 <dbl> 5.1719202, 7.5490587, 8.8205542, 5.219092…
$ popdens_county              <dbl> 35.330814, 228.777633, 228.777633, 161.26…
$ popdens_zcta                <dbl> 40.718962, 95.385827, 577.735106, 453.475…
$ nohs                        <dbl> 14.3, 7.2, 0.8, 1.2, 4.8, 16.7, 19.1, 6.4…
$ somehs                      <dbl> 16.7, 12.2, 2.6, 3.1, 7.8, 33.3, 15.6, 9.…
$ hs                          <dbl> 35.0, 32.2, 12.9, 15.1, 28.7, 37.5, 26.5,…
$ somecollege                 <dbl> 14.9, 19.0, 17.9, 20.5, 25.0, 12.5, 18.0,…
$ associate                   <dbl> 5.5, 6.8, 5.2, 6.5, 7.5, 0.0, 6.0, 8.8, 3…
$ bachelor                    <dbl> 7.9, 14.8, 35.5, 30.4, 18.2, 0.0, 10.6, 1…
$ grad                        <dbl> 5.8, 7.7, 25.2, 23.3, 8.0, 0.0, 4.1, 5.7,…
$ pov                         <dbl> 13.8, 10.5, 2.1, 5.2, 8.3, 18.8, 21.4, 14…
$ hs_orless                   <dbl> 66.0, 51.6, 16.3, 19.4, 41.3, 87.5, 61.2,…
$ urc2013                     <dbl> 6, 1, 1, 3, 4, 5, 1, 2, 5, 4, 4, 6, 6, 1,…
$ aod                         <dbl> 33.08333, 42.45455, 44.25000, 42.41667, 4…
$ state_California            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,…
$ city_Not.in.a.city          <dbl> NA, NA, NA, NA, NA, NA, 0, NA, NA, NA, NA…

Notice that our city_Not.in.a.city variable seems to be NA values. Why might that be?

Ah! Perhaps it is because some of our levels were not previously seen in the training set!

Let’s take a look using the set operations of the dplyr package. We can take a look at cities that were different between the test and training set.

[1] 376   1
[1] 51  1

Indeed, there are lots of different cities in our test data that are not in our training data!

So, let go back to our pm data set and modify the city variable to just be values of in a city or not in a city using the case_when() function of dplyr. This function allows you to vectorize multiple if_else() statements.

# A tibble: 876 x 50
   id    value fips    lat   lon state county city   CMAQ zcta  zcta_area
   <fct> <dbl> <fct> <dbl> <dbl> <chr> <chr>  <chr> <dbl> <fct>     <dbl>
 1 1003…  9.60 1003   30.5 -87.9 Alab… Baldw… In a…  8.10 36532 190980522
 2 1027… 10.8  1027   33.3 -85.8 Alab… Clay   In a…  9.77 36251 374132430
 3 1033… 11.2  1033   34.8 -87.7 Alab… Colbe… In a…  9.40 35660  16716984
 4 1049… 11.7  1049   34.3 -86.0 Alab… DeKalb In a…  8.53 35962 203836235
 5 1055… 12.4  1055   34.0 -86.0 Alab… Etowah In a…  9.24 35901 154069359
 6 1069… 10.5  1069   31.2 -85.4 Alab… Houst… In a…  9.12 36303 162685124
 7 1073… 15.6  1073   33.6 -86.8 Alab… Jeffe… In a… 10.2  35207  26929603
 8 1073… 12.4  1073   33.3 -87.0 Alab… Jeffe… Not … 10.2  35111 166239542
 9 1073… 11.1  1073   33.5 -87.3 Alab… Jeffe… Not …  8.16 35444 385566685
10 1073… 13.1  1073   33.5 -86.5 Alab… Jeffe… In a…  9.30 35094 148994881
# … with 866 more rows, and 39 more variables: zcta_pop <dbl>, imp_a500 <dbl>,
#   imp_a1000 <dbl>, imp_a5000 <dbl>, imp_a10000 <dbl>, imp_a15000 <dbl>,
#   county_area <dbl>, county_pop <dbl>, log_dist_to_prisec <dbl>,
#   log_pri_length_5000 <dbl>, log_pri_length_10000 <dbl>,
#   log_pri_length_15000 <dbl>, log_pri_length_25000 <dbl>,
#   log_prisec_length_500 <dbl>, log_prisec_length_1000 <dbl>,
#   log_prisec_length_5000 <dbl>, log_prisec_length_10000 <dbl>,
#   log_prisec_length_15000 <dbl>, log_prisec_length_25000 <dbl>,
#   log_nei_2008_pm25_sum_10000 <dbl>, log_nei_2008_pm25_sum_15000 <dbl>,
#   log_nei_2008_pm25_sum_25000 <dbl>, log_nei_2008_pm10_sum_10000 <dbl>,
#   log_nei_2008_pm10_sum_15000 <dbl>, log_nei_2008_pm10_sum_25000 <dbl>,
#   popdens_county <dbl>, popdens_zcta <dbl>, nohs <dbl>, somehs <dbl>,
#   hs <dbl>, somecollege <dbl>, associate <dbl>, bachelor <dbl>, grad <dbl>,
#   pov <dbl>, hs_orless <dbl>, urc2013 <dbl>, urc2006 <dbl>, aod <dbl>

Alternatively you could create a custom step function to do this and add this to your recipe, but that is beyond the scope of this case study.

We will need to repeat all the steps (splitting the data, pre-processing, etc) as the levels of our variables have now changed.

While we are doing this, we might also have this issue for county.

The county variables appears to get dropped due to either correlation or near zero variance.

It is likely due to near zero variance because this is the more granular of these geographic categorical variables and likely sparse.

<Analysis/Assess/Total>
<584/292/876>

Question Opportunity

See if you can come up with the code for the new recipe.


Click here to reveal the code for the new recipe.


Data Recipe

Inputs:

        role #variables
   county id          1
 id variable          1
     outcome          1
   predictor         47

Operations:

Dummy variables from state, county, city, zcta
Correlation filter on all_numeric
Sparse, unbalanced variable filter on all_numeric

Now let’s retrain our training data and try baking our test data.

Question Opportunity

Do you recall how to pre-process and extract the pre-processed training data?


Click here to reveal the answer.

oper 1 step dummy [training] 
oper 2 step corr [training] 
oper 3 step nzv [training] 
The retained training set is ~ 0.26 Mb  in memory.

Rows: 584
Columns: 36
$ id                          <fct> 1003.001, 1027.0001, 1033.1002, 1055.001,…
$ value                       <dbl> 9.597647, 10.800000, 11.212174, 12.375394…
$ fips                        <fct> 1003, 1027, 1033, 1055, 1069, 1073, 1073,…
$ lat                         <dbl> 30.49800, 33.28126, 34.75878, 33.99375, 3…
$ lon                         <dbl> -87.88141, -85.80218, -87.65056, -85.9910…
$ CMAQ                        <dbl> 8.098836, 9.766208, 9.402679, 9.241744, 9…
$ zcta_area                   <dbl> 190980522, 374132430, 16716984, 154069359…
$ zcta_pop                    <dbl> 27829, 5103, 9042, 20045, 30217, 9010, 16…
$ imp_a500                    <dbl> 0.01730104, 1.96972318, 19.17301038, 16.4…
$ imp_a15000                  <dbl> 1.4386207, 0.3359198, 5.2472094, 5.161210…
$ county_area                 <dbl> 4117521611, 1564252280, 1534877333, 13856…
$ county_pop                  <dbl> 182265, 13932, 54428, 104430, 101547, 658…
$ log_dist_to_prisec          <dbl> 4.648181, 7.219907, 5.760131, 5.261457, 7…
$ log_pri_length_5000         <dbl> 8.517193, 8.517193, 8.517193, 9.066563, 8…
$ log_pri_length_25000        <dbl> 11.32735, 10.12663, 10.15769, 12.01356, 1…
$ log_prisec_length_500       <dbl> 7.295356, 6.214608, 8.611945, 8.740680, 6…
$ log_prisec_length_1000      <dbl> 8.195119, 7.600902, 9.735569, 9.627898, 7…
$ log_prisec_length_5000      <dbl> 10.815042, 10.170878, 11.770407, 11.72888…
$ log_prisec_length_10000     <dbl> 11.886803, 11.405543, 12.840663, 12.76827…
$ log_nei_2008_pm10_sum_15000 <dbl> 2.26783411, 3.31111648, 6.70127741, 4.462…
$ log_nei_2008_pm10_sum_25000 <dbl> 5.628728, 3.311116, 7.148858, 4.678311, 3…
$ popdens_county              <dbl> 44.265706, 8.906492, 35.460814, 75.367038…
$ popdens_zcta                <dbl> 145.7164307, 13.6395554, 540.8870404, 130…
$ nohs                        <dbl> 3.3, 11.6, 7.3, 4.3, 5.8, 7.1, 2.7, 11.1,…
$ somehs                      <dbl> 4.9, 19.1, 15.8, 13.3, 11.6, 17.1, 6.6, 1…
$ hs                          <dbl> 25.1, 33.9, 30.6, 27.8, 29.8, 37.2, 30.7,…
$ somecollege                 <dbl> 19.7, 18.8, 20.9, 29.2, 21.4, 23.5, 25.7,…
$ associate                   <dbl> 8.2, 8.0, 7.6, 10.1, 7.9, 7.3, 8.0, 4.1, …
$ bachelor                    <dbl> 25.3, 5.5, 12.7, 10.0, 13.7, 5.9, 17.6, 7…
$ grad                        <dbl> 13.5, 3.1, 5.1, 5.4, 9.8, 2.0, 8.7, 2.9, …
$ pov                         <dbl> 6.1, 19.5, 19.0, 8.8, 15.6, 25.5, 7.3, 8.…
$ hs_orless                   <dbl> 33.3, 64.6, 53.7, 45.4, 47.2, 61.4, 40.0,…
$ urc2013                     <dbl> 4, 6, 4, 4, 4, 1, 1, 1, 1, 1, 2, 3, 3, 3,…
$ aod                         <dbl> 37.363636, 34.818182, 36.000000, 43.41666…
$ state_California            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
$ city_Not.in.a.city          <dbl> 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0,…

And now, let’s try baking our test set to see if we still have NA values.

Rows: 292
Columns: 36
$ id                          <fct> 1049.1003, 1073.101, 1073.2006, 1089.0014…
$ value                       <dbl> 11.659091, 13.114545, 12.228125, 12.23294…
$ fips                        <fct> 1049, 1073, 1073, 1089, 1103, 1121, 4013,…
$ lat                         <dbl> 34.28763, 33.54528, 33.38639, 34.68767, 3…
$ lon                         <dbl> -85.96830, -86.54917, -86.81667, -86.5863…
$ CMAQ                        <dbl> 8.534772, 9.303766, 10.235612, 9.343611, …
$ zcta_area                   <dbl> 203836235, 148994881, 56063756, 46963946,…
$ zcta_pop                    <dbl> 8300, 14212, 32390, 21297, 30545, 7713, 5…
$ imp_a500                    <dbl> 5.78200692, 0.06055363, 42.42820069, 23.2…
$ imp_a15000                  <dbl> 0.9730444, 2.9956557, 12.7487614, 10.3555…
$ county_area                 <dbl> 2012662359, 2878192209, 2878192209, 20761…
$ county_pop                  <dbl> 71109, 658466, 658466, 334811, 119490, 82…
$ log_dist_to_prisec          <dbl> 3.721489, 7.301545, 4.721755, 4.659519, 6…
$ log_pri_length_5000         <dbl> 8.517193, 9.683336, 10.737240, 8.517193, …
$ log_pri_length_25000        <dbl> 11.90959, 12.53777, 12.99669, 11.47391, 1…
$ log_prisec_length_500       <dbl> 7.310155, 6.214608, 7.528913, 8.760549, 6…
$ log_prisec_length_1000      <dbl> 8.585843, 7.600902, 9.342290, 9.543183, 8…
$ log_prisec_length_5000      <dbl> 10.214200, 11.262645, 11.713190, 11.48606…
$ log_prisec_length_10000     <dbl> 11.50894, 12.14101, 12.53899, 12.68440, 1…
$ log_nei_2008_pm10_sum_15000 <dbl> 3.3500444, 6.6241114, 5.8268686, 3.861625…
$ log_nei_2008_pm10_sum_25000 <dbl> 5.1719202, 7.5490587, 8.8205542, 5.219092…
$ popdens_county              <dbl> 35.330814, 228.777633, 228.777633, 161.26…
$ popdens_zcta                <dbl> 40.718962, 95.385827, 577.735106, 453.475…
$ nohs                        <dbl> 14.3, 7.2, 0.8, 1.2, 4.8, 16.7, 19.1, 6.4…
$ somehs                      <dbl> 16.7, 12.2, 2.6, 3.1, 7.8, 33.3, 15.6, 9.…
$ hs                          <dbl> 35.0, 32.2, 12.9, 15.1, 28.7, 37.5, 26.5,…
$ somecollege                 <dbl> 14.9, 19.0, 17.9, 20.5, 25.0, 12.5, 18.0,…
$ associate                   <dbl> 5.5, 6.8, 5.2, 6.5, 7.5, 0.0, 6.0, 8.8, 3…
$ bachelor                    <dbl> 7.9, 14.8, 35.5, 30.4, 18.2, 0.0, 10.6, 1…
$ grad                        <dbl> 5.8, 7.7, 25.2, 23.3, 8.0, 0.0, 4.1, 5.7,…
$ pov                         <dbl> 13.8, 10.5, 2.1, 5.2, 8.3, 18.8, 21.4, 14…
$ hs_orless                   <dbl> 66.0, 51.6, 16.3, 19.4, 41.3, 87.5, 61.2,…
$ urc2013                     <dbl> 6, 1, 1, 3, 4, 5, 1, 2, 5, 4, 4, 6, 6, 1,…
$ aod                         <dbl> 33.08333, 42.45455, 44.25000, 42.41667, 4…
$ state_California            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,…
$ city_Not.in.a.city          <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…

Great now we no longer have NA values! :)

Note: if you use the skip option for some of the pre-processing steps, be careful. juice() will show all of the results ignoring skip = TRUE. bake() will not necessarily conduct these steps on the new data.

Specifying the model

So far we have used the packages rsample to split the data and recipes to assign variable types, and to specify and prep our pre-processing (as well as to optionally extract the pre-processed data).

We will now use the parsnip package (which is similar to the previous caret package - and hence why it is named after the vegetable) to specify our model.

There are four things we need to define about our model:

  1. The type of model (using specific functions in parsnip like rand_forest(), logistic_reg() etc.)
  2. The package or engine that we will use to implement the type of model selected (using the set_engine() function)
  3. The mode of learning - classification or regression (using the set_mode() function)
  4. Any arguments necessary for the model/package selected (using the set_args()function - for example the mtry = argument for random forest which is the number of variables to be used as options for splitting at each tree node)

Let’s walk through these steps one by one. For our case, we are going to start our analysis with a linear regression but we will demonstrate how we can try different models.

The first step is to define what type of model we would like to use. See here for modeling options in parsnip.

Linear Regression Model Specification (regression)

OK. So far, all we have defined is that we want to use a linear regression…
Let’s tell parsnip more about what we want.

We would like to use the ordinary least squares method to fit our linear regression. So we will tell parsnip that we want to use the lm package to implement our linear regression (there are many options actually such as rstan glmnet, keras, and sparklyr). See here for a description of the differences and using these different engines with parsnip.

We will do so by using the set_engine() function of the parsnip package.

Linear Regression Model Specification (regression)

Computational engine: lm 

In some cases some packages can do either classification or prediction, so it is a good idea to specify which mode you intend to perform. Here, we aim to predict the air pollution. You can do this with the set_mode() function of the parsnip package, by using either set_mode("classification") or set_mode("regression").

Linear Regression Model Specification (regression)

Computational engine: lm 

Fitting the model

We can use the parsnip package with a newer package called workflows to fit our model.

The workflows package allows us to keep track of both our pre-processing steps and our model specification. It also allows us to implement fancier optimizations in an automated way and it can also handle post-processing operations.

We begin by creating a workflow using the workflow() function in the workflows package.

Next, we use add_recipe() (our pre-processing specifications) and we add our model with the add_model() function – both functions from the workflows package.

Note: We do not need to actually prep() our recipe before using workflows!

If you recall novel_rec is the recipe we previously created with the recipes package and lm_PM_model was created when we specified our model with the parsnip package. Here, we combine everything together into a workflow().

══ Workflow ═════════════════════════════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: linear_reg()

── Preprocessor ─────────────────────────────────────────────────────────────────────────────
3 Recipe Steps

● step_dummy()
● step_corr()
● step_nzv()

── Model ────────────────────────────────────────────────────────────────────────────────────
Linear Regression Model Specification (regression)

Computational engine: lm 

Ah, nice. Notice how it tells us about both our pre-processing steps and our model specifications.

Next, we “prepare the recipe” (or estimate the parameters) and fit the model to our training data all at once. Printing the output, we can see the coefficients of the model.

══ Workflow [trained] ═══════════════════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: linear_reg()

── Preprocessor ─────────────────────────────────────────────────────────────────────────────
3 Recipe Steps

● step_dummy()
● step_corr()
● step_nzv()

── Model ────────────────────────────────────────────────────────────────────────────────────

Call:
stats::lm(formula = formula, data = data)

Coefficients:
                (Intercept)                          lat  
                  2.221e+02                    3.773e-02  
                        lon                         CMAQ  
                  3.023e-02                    2.896e-01  
                  zcta_area                     zcta_pop  
                  3.592e-10                    7.988e-06  
                   imp_a500                   imp_a15000  
                  6.543e-03                   -1.252e-03  
                county_area                   county_pop  
                 -2.241e-11                   -2.052e-07  
         log_dist_to_prisec          log_pri_length_5000  
                  4.182e-02                   -2.207e-01  
       log_pri_length_25000        log_prisec_length_500  
                  9.277e-02                    2.651e-01  
     log_prisec_length_1000       log_prisec_length_5000  
                 -1.654e-02                    4.451e-01  
    log_prisec_length_10000  log_nei_2008_pm10_sum_15000  
                  1.510e-01                    1.029e-01  
log_nei_2008_pm10_sum_25000               popdens_county  
                  6.448e-02                   -3.905e-05  
               popdens_zcta                         nohs  
                 -2.098e-05                   -2.195e+00  
                     somehs                           hs  
                 -2.232e+00                   -2.235e+00  
                somecollege                    associate  
                 -2.237e+00                   -2.237e+00  
                   bachelor                         grad  
                 -2.239e+00                   -2.248e+00  
                        pov                    hs_orless  
                  5.400e-03                           NA  
                    urc2013                          aod  
                  1.848e-01                    2.521e-02  
           state_California           city_Not.in.a.city  
                  3.509e+00                    4.137e-01  

Click here to see the steps that the workflows package performs that used to be required

AVOCADO:this section feels like it should either have more explanation or just deleted. To me it feels confusing because it feels like we are showing a completely alternative path without an explanation why you might want to do? or when you would want to do this?

avocado response-what do you think of how I titled this section - if you still don’t like it feel free to delete

Previously the processed training data (juiced_train) as opposed to the raw training data would be required to fit the model.

In this case, we would actually also needed to write your model again! Recall that id and fips are ID variables and that values is our outcome of interest (the air pollution measure at each monitor). It is nice that workflows keeps track of this!

Assessing the model fit

After we fit our model, we can use the broom package to look at the output from the fitted model in an easy/tidy.

The tidy() function returns a tidy data frame with coefficients from the model (one row per coefficient).

Many other broom functions currently only work with parsnip objects, not raw workflows objects.

However, we can use the tidy function if we first use the pull_workflow_fit() function.

# A tibble: 33 x 5
   term         estimate std.error statistic  p.value
   <chr>           <dbl>     <dbl>     <dbl>    <dbl>
 1 (Intercept)  2.22e+ 2  1.20e+ 2     1.85  6.51e- 2
 2 lat          3.77e- 2  2.45e- 2     1.54  1.24e- 1
 3 lon          3.02e- 2  9.31e- 3     3.25  1.23e- 3
 4 CMAQ         2.90e- 1  4.29e- 2     6.75  3.66e-11
 5 zcta_area    3.59e-10  3.04e-10     1.18  2.38e- 1
 6 zcta_pop     7.99e- 6  5.55e- 6     1.44  1.50e- 1
 7 imp_a500     6.54e- 3  7.49e- 3     0.874 3.82e- 1
 8 imp_a15000  -1.25e- 3  1.19e- 2    -0.105 9.16e- 1
 9 county_area -2.24e-11  1.63e-11    -1.38  1.69e- 1
10 county_pop  -2.05e- 7  9.31e- 8    -2.20  2.79e- 2
# … with 23 more rows

We have fit our model on our training data, which means we have created a model to predict values of air pollution based on the predictors that we have included. Yay!

One last thing before we leave this section. We often are interested in getting a sense of which variables are the most important in our model. We can explore the variable importance using the vip() function of the vip package. This function create a bar plot of variable importance scores for each predictor variable (or feature) in a model. The bar plot is ordered by importance (highest to smallest).

Notice again that we need to use the pull_workflow_fit() function.

Let’s take a look at the top 10 contributing variables:

The state in which the monitor was located and the CMAQ model and the aod satellite information appear to be the most important for predicting the air pollution at a given monitor.

Model performance

In this next section, our goal is to assess the overall model performance. The way we do this is to compare the similarity between the predicted estimates of the outcome variable produced by the model and the true outcome variable values.

If you recall the What is machine learning? section, we showed how to think about machine learning (ML) as an optimization problem that tries to minimize the distance between our predicted outcome \(\hat{Y} = f(X)\) and actual outcome \(Y\) using our features (or predictor variables) \(X\) as input to a function \(f\) that we want to estimate.

\[d(Y - \hat{Y})\]

As our goal in this section is to assess overall model performance, we will now talk about different distance metrics that you can use.

First, let’s pull out our predicted outcome values \(\hat{Y} = f(X)\) from the models we fit (using different approaches).

        1         2         3         4         5         6 
 9.480088 10.418317 11.786217 11.295655 10.806653 11.091767 

Alternatively, we can get the fitted values using the augment() function of the broom package using the output from workflows:

# A tibble: 6 x 8
  value .fitted .se.fit .resid   .hat .sigma    .cooksd .std.resid
  <dbl>   <dbl>   <dbl>  <dbl>  <dbl>  <dbl>      <dbl>      <dbl>
1  9.60    9.48   0.375  0.118 0.0323   2.09 0.00000333     0.0573
2 10.8    10.4    0.383  0.382 0.0338   2.09 0.0000368      0.186 
3 11.2    11.8    0.404 -0.574 0.0376   2.09 0.0000933     -0.281 
4 12.4    11.3    0.366  1.08  0.0309   2.09 0.000267       0.526 
5 10.5    10.8    0.424 -0.298 0.0413   2.09 0.0000278     -0.146 
6 15.6    11.1    0.380  4.50  0.0332   2.08 0.00501        2.20  

Note that becuase we use the actual workflow here, we can (and actually need to) use the raw data instead of the pre-processed data.

# A tibble: 584 x 5
   .pred value fips  county    id       
   <dbl> <dbl> <fct> <chr>     <fct>    
 1  9.48  9.60 1003  Baldwin   1003.001 
 2 10.4  10.8  1027  Clay      1027.0001
 3 11.8  11.2  1033  Colbert   1033.1002
 4 11.3  12.4  1055  Etowah    1055.001 
 5 10.8  10.5  1069  Houston   1069.0003
 6 11.1  15.6  1073  Jefferson 1073.0023
 7 10.2  12.4  1073  Jefferson 1073.1005
 8  8.28 11.1  1073  Jefferson 1073.1009
 9 11.5  14.6  1073  Jefferson 1073.2003
10 10.6  12.0  1073  Jefferson 1073.5002
# … with 574 more rows

Visualizing model performance

Now, we can compare the predicted outcome values (or fitted values) \(\hat{Y}\) to the actual outcome values \(Y\) that we observed:

OK, so our range of the predicted outcome values appears to be smaller than the real values. We could probably do a bit better.

Quantifying model performance

Next, let’s use different distance functions \(d(\cdot)\) to assess how far off our predicted outcome \(\hat{Y} = f(X)\) and actual outcome \(Y\) values are from each other:

\[d(Y - \hat{Y})\]

As mentioned, there are entire scholarly fields of research dedicated to identifying different distance metrics \(d(\cdot)\) for machine learning applications. However, when performing prediction with a continuous outcome \(Y\), a few of the mostly commonly used distance metrics are:

  1. mean absolute error (mae)

\[MAE = \frac{\sum_{i=1}^{n}{(|\hat{y_t}- y_t|)}^2}{n}\]

  1. R squared error (rsq) – this is also known as the coefficient of determination which is the squared correlation between truth and estimate

This is calculated and 1 minus the fraction of the residual sum of squares (\(SS_res\)) by the total sum of squares (\(SS_tot\))

\[RSQ = R^2 = 1 - \frac{SSres}{SStot}\]

\[SS_{tot} = \sum_{i=1}^{n}{(y_i- \bar{y})}^2\] The total sum of squares is proportional to the variance of the data. It is calculated as the sum of each true value from the mean true value (\(\bar{y}\)).

\[SS_{res} = \sum_{i=1}^{n}{(y_i- \hat{y_i})}^2\]

The sum of squares of residuals is calculated as the sum of each predicted value (\(\hat{y_i}\) or sometimes \(f_i\)) from the true value (\(y_i\)).

  1. root mean squared error (rmse)

\[RMSE = \sqrt{\frac{\sum_{i=1}^{n}{(\hat{y_t}- y_t)}^2}{n}}\]

One way to calculate these metrics within the tidymodels framework is to use the yardstick package using the metrics() function.

# A tibble: 3 x 3
  .metric .estimator .estimate
  <chr>   <chr>          <dbl>
1 rmse    standard       2.02 
2 rsq     standard       0.431
3 mae     standard       1.49 

Alternatively if you only wanted one metric you could use the mae(), rsq(), or rmse() functions, respectively.

# A tibble: 1 x 3
  .metric .estimator .estimate
  <chr>   <chr>          <dbl>
1 mae     standard        1.49

Cross validation

Until now we have used everything in our “training” dataset (and have not touched the “testing” dataset) from the rsample package to build our machine learning (ML) model \(\hat{Y} = f(X)\) (or to estimate \(f\) using the features or predictor variable \(X\)).

Here, we take move this beyond the simple split into training and testing data sets. We will again use the rsample package again in order to further implement what are called cross validation techniques. This is also called re-sampling or repartioning.

Note: we are not actually getting new samples from the underlying distribution so the term re-sampling is a bit of a misnomer.

Cross validation splits our training data into multiple training data sets to allow for a deeper assessment of the accuracy of the model.

Here is a visualization of the concept for cross validation/resampling/repartitioning from Max Kuhn:

Technically creating our testing and training set out of our original training data is sometimes considered a form of cross validation, called the holdout method. The reason we do this it so we can get a better sense of the accuracy of our model using data that we did not train it on.

However, we can actually do a better job of optimizing our model for accuracy if we also perform another type of cross validation on the newly defined training set that we just created. There are many cross validation methods and most can be easily implemented using rsample package. Here, we will use a very popular method called either k-fold or v-fold cross validation.

This method involves essentially preforming the hold out method iteratively with the training data.

First, the training set is divided into \(v\) (or often called called \(k\)) equally sized smaller pieces.

Next, the model is trained on the model on \(v\)-1 subsets of the data iteratively (removing a different \(v\) until all possible \(v\)-1 sets have been evaluated) to get a sense of the performance of the model. This is really useful for fine tuning specific aspects of the model in a process called model tuning, which we will learn about in the next section.

Here is a visualization of how the folds are created:

Note: People typically ignore spatial dependence with cross validation of air pollution monitoring data in the air pollution field, so we will do the same. However, it might make sense to leave out blocks of monitors rather than random individual monitors to help account for some spatial dependence.

Creating the \(v\)-folds using rsample

The vfold_cv() function of the rsample package can be used to parse the training data into folds for \(v\)-fold cross validation.

  • The v argument specifies the number of folds to create.
  • The repeats argument specifies if any samples should be repeated across folds - default is FALSE
  • The strata argument specifies a variable to stratify samples across folds - just like in initial_split().

Again, because these are created at random, we need to use the base set.seed() function in order to obtain the same results each time we knit this document. Generally speaking using 10 folds is good practice, but this depends on the variability within your data. We are going to use 4 for the sake of expediency.

#  4-fold cross-validation 
# A tibble: 4 x 2
  splits            id   
  <list>            <chr>
1 <split [438/146]> Fold1
2 <split [438/146]> Fold2
3 <split [438/146]> Fold3
4 <split [438/146]> Fold4
[[1]]
<Analysis/Assess/Total>
<438/146/584>

[[2]]
<Analysis/Assess/Total>
<438/146/584>

[[3]]
<Analysis/Assess/Total>
<438/146/584>

[[4]]
<Analysis/Assess/Total>
<438/146/584>

Now we can see that we have created 4 folds of the data and we can see how many values were set aside for testing (called assessing for cross validation sets) and training (called analysis for cross validation sets) within each fold.

Once the folds are created they can be used to evaluate performance by fitting the model to each of the re-samples that we created:

Assessing model performance on \(v\)-folds using tune

We can fit the model to our cross validation folds using the fit_resamples() function of the tune package, by specifying our workflow object and the cross validation fold object we just created. See here for more information.

We can now take a look at various performance metrics based on the fit of our cross validation “resamples”. To do this we will use the show_best() function of the tune package.

# A tibble: 1 x 5
  .metric .estimator  mean     n std_err
  <chr>   <chr>      <dbl> <int>   <dbl>
1 rmse    standard    2.17     4  0.0746

Here we can see the mean RMSE value across all four folds. The function is called show_best() because it is also used for model tuning and it will show the parameter combination with the best performance, we will discuss this more later in the case study.

Data Analysis


In the previous section, we demonstrated how to build a machine learning model (specifically a linear regression model) to predict air pollution with the tidymodels framework.

In the next few section, we will demonstrate another machine learning model.

Random Forest

Now, we are going to predict our outcome variable (air pollution) using a decision tree method called random forest.

A decision tree is a tool to partition data or anything really, based on a series of sequential (often binary) decisions, where the decisions are chosen based on their ability to optimally split the data.

Here you can see a simple example:

[source]

In the case of random forest, multiple decision trees are created - hence the name forest, and each tree is built using a random subset of the training data (with replacement) - hence the full name random forest. This random aspect helps to keep the algorithm from overfitting the data.

The mean of the predictions from each of the trees is used in the final output.

In our case, we are going to use the random forest method of the the randomForest package.

This package is currently not compatible with categorical variables that have more than 53 levels. See here for the documentation about when this was updated from 25 levels. Thus we will to remove the zcta and county variables.

Note that the step_novel() function is necessary here for state to get all cross validation folds to work, because there will be different levels included in each fold test and training sets, thus there are new levels for some of the test sets which will result in an error.

According to the documentation for the recipes package:

step_novel creates a specification of a recipe step that will assign a previously unseen factor level to a new value.

The rand_forest() function of the parsnip package has three important arguments that act as an interface for the different possible engines to perform a random forest analysis:

  1. mtry - The number of predictor variables (or features) that will be randomly sampled at each split when creating the tree models. The default number for regression analyses is the number of predictors divided by 3.
  2. min_n - The minimum number of data points in a node that are required for the node to be split further.
  3. trees - the number of trees in the ensemble

We will start by trying an mtry value of 10 and a min_n value of 3.

Now that we have our recipe (RF_rec), let’s specify the model with rand_forest() from parsnip with the mode = "regression" argument to specify our outcome variable (air pollution) is continuous.

Random Forest Model Specification (regression)

Main Arguments:
  mtry = 10
  min_n = 3

Next, we set the engine and mode:

Note that you could also use the ranger or spark packages instead of randomForest. If you were to use the ranger package to implement the random forest analysis you would need to specify an importance argument to be able to evaluate predictor importance. The options are impurity or permutation.

These other packages have different advantages and disadvantages- for example ranger and spark are not as limiting for the number of categories for categorical variables. For more information see their documentation: here for ranger, here for spark, and here for randomForest.

See here for more documentation about implementing these engine options with tidymodels. Note that there are also other R packages for implementing random forest algorithms, but these three packages (ranger, spark, and randomForest) are currently compatible with tidymodels.

Random Forest Model Specification (regression)

Main Arguments:
  mtry = 10
  min_n = 3

Computational engine: randomForest 

Then, we put this all together into a workflow:

Question Opportunity

See if you can come up with the code to do this.


Click here to reveal the answer.


══ Workflow ═════════════════════════════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: rand_forest()

── Preprocessor ─────────────────────────────────────────────────────────────────────────────
6 Recipe Steps

● step_novel()
● step_string2factor()
● step_rm()
● step_rm()
● step_corr()
● step_nzv()

── Model ────────────────────────────────────────────────────────────────────────────────────
Random Forest Model Specification (regression)

Main Arguments:
  mtry = 10
  min_n = 3

Computational engine: randomForest 

Finally, we fit the data to the model: #### {.recall_code_question_block} Question Opportunity

Do you recall how to do this?


Click here to reveal the answer.


══ Workflow [trained] ═══════════════════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: rand_forest()

── Preprocessor ─────────────────────────────────────────────────────────────────────────────
6 Recipe Steps

● step_novel()
● step_string2factor()
● step_rm()
● step_rm()
● step_corr()
● step_nzv()

── Model ────────────────────────────────────────────────────────────────────────────────────

Call:
 randomForest(x = as.data.frame(x), y = y, mtry = ~10, nodesize = ~3) 
               Type of random forest: regression
                     Number of trees: 500
No. of variables tried at each split: 10

          Mean of squared residuals: 2.841335
                    % Var explained: 60.57

Let’s take a look at the top 10 contributing variables:

Question Opportunity

See if you can recall how to do this.


Click here to reveal the answer.


Interesting! In the previous model the CMAQ values and the state where the monitor was located were also the top two most important, however predictors about education levels of the communities where the monitor was located was among the top most important. Now we see that population density and proximity to sources of emissions and roads are among the top ten.

Now let’s take a look at model performance by fitting the data using cross validation:

Question Opportunity

See if you can recall how to do this.


Click here to reveal the answer.


# A tibble: 2 x 5
  .metric .estimator  mean     n std_err
  <chr>   <chr>      <dbl> <int>   <dbl>
1 rmse    standard   1.71      4  0.118 
2 rsq     standard   0.616     4  0.0379

Now let’s compare the performance of this model with our linear regression model:

# A tibble: 2 x 5
  .metric .estimator  mean     n std_err
  <chr>   <chr>      <dbl> <int>   <dbl>
1 rmse    standard   2.17      4  0.0746
2 rsq     standard   0.366     4  0.0493

OK, so our first model had a mean rmse value of 2.17. It looks like the random forest model had a much lower rmse value of 1.72.

Question Opportunity

Do you recall how the RMSE is calculated? ####


Click here to reveal the answer. \[RMSE = \sqrt{\frac{\sum_{i=1}^{n}{(\hat{y_t}- y_t)}^2}{n}}\]

If we tuned our random forest model based on the number of trees or the value for mtry (which is “The number of predictors that will be randomly sampled at each split when creating the tree models”), we might get a model with even better performance.

However, our cross validated mean rmse value of 1.72 is quite good because our range of true outcome values is much larger: (3.496, 22.259).

Model tuning

Hyperparameters are often things that we need to specify about a model. For example, the number of predictor variables (or features) that will be randomly sampled at each split when creating the tree models called mtry is a hyperparameter. The default number for regression analyses is the number of predictors divided by 3. Instead of arbitrarily specifying this, we can try to determine the best option for model performance by a process called tuning.

Now let’s try some tuning.

Let’s take a closer look at the mtry and min_n hyperparametrs in our Random Forest model.

We aren’t exactly sure what values of mtry and min_n achieve good accuracy yet keep our model generalizable for other data.

This is when our cross validation methods become really handy because now we can test out different values for each of these hyperparameters to assess what values seem to work best for model performance on these resamples of our training set data.

Previously we specified our model like so:

Random Forest Model Specification (regression)

Main Arguments:
  mtry = 10
  min_n = 3

Computational engine: randomForest 

Now instead of specifying a value for the mtry and min_n arguments, we can use the tune() function of the tune package like so: mtry = tune(). This indicates that these hyperparameters are to be tuned.

Random Forest Model Specification (regression)

Main Arguments:
  mtry = tune()
  min_n = tune()

Computational engine: randomForest 

Again we will add this to a workflow, the only difference here is that we are using a different model specification with tune_RF_model instead of RF_model:

══ Workflow ═════════════════════════════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: rand_forest()

── Preprocessor ─────────────────────────────────────────────────────────────────────────────
6 Recipe Steps

● step_novel()
● step_string2factor()
● step_rm()
● step_rm()
● step_corr()
● step_nzv()

── Model ────────────────────────────────────────────────────────────────────────────────────
Random Forest Model Specification (regression)

Main Arguments:
  mtry = tune()
  min_n = tune()

Computational engine: randomForest 

Now we can use the tune_grid() function of the tune package to evaluate different combinations of values for mtry and min_n using our cross validation samples of our training set (vfold_pm) to see what combination of values performs best.

To use this function we will specify the workflow using the object argument and the samples to use using the resamples argument. The grid argument specifies how many possible options for each argument should be attempted.

By default 10 different values will be attempted for each hyperparameter that is being tuned.

We can use the doParallel package to allow us to fit all these models to our cross validation samples faster. So if you were performing this on a computer with multiple cores or processors, then different models with different hyperparameter values can be fit to the cross validation samples simultaneously across different cores or processors.

We need to use set.seed() here because the values chosen for mtry and min_n may vary if we preform this evaluation again because they are chosen semi-randomly (meaning that they are within a range of reasonable values but still random).

Note: this step will take some time.

#  4-fold cross-validation 
# A tibble: 4 x 4
  splits            id    .metrics          .notes          
  <list>            <chr> <list>            <list>          
1 <split [438/146]> Fold1 <tibble [40 × 5]> <tibble [0 × 1]>
2 <split [438/146]> Fold2 <tibble [40 × 5]> <tibble [0 × 1]>
3 <split [438/146]> Fold3 <tibble [40 × 5]> <tibble [0 × 1]>
4 <split [438/146]> Fold4 <tibble [40 × 5]> <tibble [0 × 1]>

See the tune getting started guide for more information about implementing this in tidymodels.

If you wanted more control over this process you could specify how the different possible options for mtry and min_n in the tune_grid() function using the grid_*()functions of thedials` package to create a more specific grid.

Be default the values for the hyperparameters being tuned are chosen semi-randomly (meaning that they are within a range of reasonable values but still random)..

Now we can use the collect_metrics() function again to take a look at what happened with our cross validation tests. We can see the different values chosen for mtry and min_n and the mean rmse and rsq values across the cross validation samples.

# A tibble: 40 x 7
    mtry min_n .metric .estimator  mean     n std_err
   <int> <int> <chr>   <chr>      <dbl> <int>   <dbl>
 1     1    27 rmse    standard   2.10      4  0.111 
 2     1    27 rsq     standard   0.470     4  0.0524
 3     4    30 rmse    standard   1.87      4  0.112 
 4     4    30 rsq     standard   0.566     4  0.0461
 5     6    32 rmse    standard   1.82      4  0.114 
 6     6    32 rsq     standard   0.577     4  0.0425
 7     7    18 rmse    standard   1.77      4  0.114 
 8     7    18 rsq     standard   0.596     4  0.0385
 9     8    23 rmse    standard   1.79      4  0.110 
10     8    23 rsq     standard   0.580     4  0.0392
# … with 30 more rows

We can now use the show_best() function as it was truly intended, to see what values for min_n and mtry resulted in the best performance.

# A tibble: 1 x 7
   mtry min_n .metric .estimator  mean     n std_err
  <int> <int> <chr>   <chr>      <dbl> <int>   <dbl>
1    17     4 rmse    standard    1.71     4   0.123

There we have it… looks like an mtry of 17 and min_n of 4 had the best rmse value. You can verify this in the above output, but it is easier to just pull this row out using this function. We can see that the mean rmse value across the cross validation sets was 1.720. Before tuning it was 1.725 with a similar std_err so the performance was very slightly improved.

Final model performance evaluation

Now that we have decided that we have reasonable performance with our training data, we can stop building our model and evaluate performance with our testing data.

Here, we will use the random forest model that we built to predict values for the monitors in the testing data and we will use the values for mtry and min_n that we just determined based on our tuning analysis to achieve the best performance.

So, first we need to specify these values in a workflow. We can use the select_best() function of the tune package to grab the values that were determined to be best for mtry and min_n.

# A tibble: 1 x 2
   mtry min_n
  <int> <int>
1    17     4

Now we can finalize the model/workflow that we we used for tuning with these values.

With the workflows package, we can use the splitting information for our original data pm_split to fit the final model on the full training set and also on the testing data using the last_fit() function of the tune package. No pre-processing steps are required.

The results will show the performance using the testing data.

The overallfit output has a lot of really useful information about how the model, the data test and training split, and the predictions for the testing data.

To see the performance on the test data we can use the collect_metrics() function like we did before.

# A tibble: 2 x 3
  .metric .estimator .estimate
  <chr>   <chr>          <dbl>
1 rmse    standard       1.44 
2 rsq     standard       0.636

Awesome! We can see that our rmse of 1.44 is quite similar with our testing data cross validation sets. We achieved quite good performance, which suggests that we would could predict other locations with more sparse monitoring based on our predictors with reasonable accuracy.

Now if you wanted to take a look at the predicted values for the test set (the 292 rows with predictions out of the 876 original monitor values) you can use the collect_predictions() function of the tune() package:

# A tibble: 292 x 4
    id               .pred  .row value
    <chr>            <dbl> <int> <dbl>
  1 train/test split 11.0      4 11.7 
  2 train/test split 11.9     10 13.1 
  3 train/test split 12.1     12 12.2 
  4 train/test split 11.6     15 12.2 
  5 train/test split 11.5     19 11.4 
  6 train/test split 12.1     22 12.2 
  7 train/test split  9.89    30 10.9 
  8 train/test split 10.4     36 10.6 
  9 train/test split  7.84    39 14.1 
 10 train/test split  7.02    40  5.83
 11 train/test split  9.10    41  9.93
 12 train/test split 11.1     43 10.7 
 13 train/test split 10.6     49 10.5 
 14 train/test split 11.8     58 11.6 
 15 train/test split 13.0     59  9.54
 16 train/test split 11.9     60  9.54
 17 train/test split 11.2     63 11.9 
 18 train/test split 14.3     66 22.3 
 19 train/test split 10.9     70 10.2 
 20 train/test split 10.9     71  8.23
 21 train/test split 10.5     77  7.09
 22 train/test split 13.0     79 17.1 
 23 train/test split 13.8     83 16.1 
 24 train/test split 13.2     84 11.8 
 25 train/test split 13.8     86 15.0 
 26 train/test split 13.1     88 14.2 
 27 train/test split 11.9     97 10.4 
 28 train/test split 12.1     98 11.2 
 29 train/test split 10.1    100 14.2 
 30 train/test split 13.0    101 13.3 
 31 train/test split 11.5    102  8.25
 32 train/test split  9.54   103  6.98
 33 train/test split 14.5    104 16.4 
 34 train/test split 13.7    105 18.2 
 35 train/test split 12.7    107 12.0 
 36 train/test split 13.0    108 13.3 
 37 train/test split 12.2    116 13.4 
 38 train/test split  9.10   123  7.61
 39 train/test split  8.84   126 10.4 
 40 train/test split  9.48   131 15.3 
 41 train/test split 12.4    136 13.0 
 42 train/test split 11.0    138 10.4 
 43 train/test split 10.4    141 10.0 
 44 train/test split 12.1    142 10.6 
 45 train/test split  8.16   145  7.69
 46 train/test split  8.05   147  7.99
 47 train/test split  7.83   155  7.34
 48 train/test split 11.1    160 11.8 
 49 train/test split 10.3    163 10.5 
 50 train/test split 11.1    166 11.5 
 51 train/test split 11.1    169 10.6 
 52 train/test split 10.5    170 11.6 
 53 train/test split 10.5    171 10.1 
 54 train/test split 11.8    178 12.2 
 55 train/test split 12.0    181 11.7 
 56 train/test split  8.42   182  7.26
 57 train/test split  8.78   184  9.91
 58 train/test split  8.38   188  8.13
 59 train/test split  8.44   189  7.82
 60 train/test split  8.62   194  8.23
 61 train/test split  8.28   195  7.02
 62 train/test split  7.75   203  6.08
 63 train/test split  8.17   205  8.08
 64 train/test split  8.64   207  8.10
 65 train/test split  8.47   208  7.55
 66 train/test split  8.60   210  8.00
 67 train/test split 12.7    211 12.3 
 68 train/test split 11.9    212 12.2 
 69 train/test split 12.0    214 11.5 
 70 train/test split 11.9    215 12.3 
 71 train/test split 13.4    216 13.8 
 72 train/test split 12.4    218 13.1 
 73 train/test split 12.2    221 12.9 
 74 train/test split 12.2    227 11.9 
 75 train/test split 12.2    228 11.7 
 76 train/test split 12.3    231 12.6 
 77 train/test split 13.0    234 13.7 
 78 train/test split  8.65   243  6.25
 79 train/test split 10.3    248 11.0 
 80 train/test split 12.1    249 12.6 
 81 train/test split 11.8    250 11.8 
 82 train/test split 12.3    251 12.2 
 83 train/test split 12.3    254 12.9 
 84 train/test split 12.0    258 11.3 
 85 train/test split 12.4    260 13.2 
 86 train/test split 10.5    262 12.4 
 87 train/test split 11.0    265 10.4 
 88 train/test split 12.0    270 11.9 
 89 train/test split 11.0    276 10.4 
 90 train/test split 11.1    280 11.1 
 91 train/test split 11.9    281 11.7 
 92 train/test split 12.1    284 11.7 
 93 train/test split 12.8    291 12.6 
 94 train/test split 11.7    293 11.4 
 95 train/test split 12.0    294 11.5 
 96 train/test split 12.5    296 11.9 
 97 train/test split 12.3    297 12.5 
 98 train/test split 12.4    298 13.9 
 99 train/test split 11.8    303 11.2 
100 train/test split 13.2    305 15.1 
101 train/test split 12.7    307 13.1 
102 train/test split 13.0    309 13.3 
103 train/test split 11.6    313 11.8 
104 train/test split 12.1    319 11.9 
105 train/test split 10.8    326 10.3 
106 train/test split  9.58   328  8.81
107 train/test split 11.7    335 11.2 
108 train/test split  9.87   338  9.85
109 train/test split  9.74   343  9.63
110 train/test split  9.72   344  9.47
111 train/test split  9.80   345  9.39
112 train/test split 11.0    350 12.5 
113 train/test split 12.1    358 12.1 
114 train/test split 11.6    361 11.7 
115 train/test split 12.9    362 13.2 
116 train/test split 13.1    363 13.4 
117 train/test split 12.2    365 12.7 
118 train/test split 11.3    371 12.5 
119 train/test split 11.0    375 11.4 
120 train/test split  9.64   378  9.57
121 train/test split 10.1    379  9.15
122 train/test split 10.5    380  9.28
123 train/test split  9.86   382  9.23
124 train/test split  9.96   389  5.58
125 train/test split 12.8    390 12.7 
126 train/test split 11.9    395  9.64
127 train/test split 12.0    397 12.2 
128 train/test split 12.6    400 12.2 
129 train/test split 13.0    403 14.3 
130 train/test split  9.26   405  9.96
131 train/test split  9.61   413  8.70
132 train/test split 10.7    417  9.82
133 train/test split 10.7    421  9.64
134 train/test split 10.3    424 10.8 
135 train/test split 10.9    427 10.1 
136 train/test split 11.0    428  9.87
137 train/test split 10.7    429 11.2 
138 train/test split 11.0    430 11.1 
139 train/test split 10.9    431 10.6 
140 train/test split 10.4    432  9.93
141 train/test split  9.37   434  7.61
142 train/test split 11.1    440 11.1 
143 train/test split 10.9    441 11.4 
144 train/test split 11.9    442 11.9 
145 train/test split 12.3    444 11.9 
146 train/test split 12.7    449 11.8 
147 train/test split 12.4    450 12.3 
148 train/test split  9.93   456 10.1 
149 train/test split  7.64   457  7.01
150 train/test split 10.2    460 11.1 
151 train/test split 10.2    461 10.9 
152 train/test split  8.01   463  6.70
153 train/test split 10.6    468 10.7 
154 train/test split 10.8    469 11.8 
155 train/test split 10.6    471 13.0 
156 train/test split 10.8    474 12.1 
157 train/test split 10.6    475 10.1 
158 train/test split 10.9    476 13.1 
159 train/test split 11.0    478 11.8 
160 train/test split 11.0    479 11.6 
161 train/test split 10.6    480 12.0 
162 train/test split 10.2    482 10.1 
163 train/test split 10.9    484 10.8 
164 train/test split 12.6    485 12.5 
165 train/test split 11.6    486 11.4 
166 train/test split 12.4    488 12.2 
167 train/test split 12.8    491 12.7 
168 train/test split  8.43   495  9.26
169 train/test split  7.41   497  5.34
170 train/test split  7.15   499  6.89
171 train/test split  7.67   501  7.32
172 train/test split  8.32   508  7.26
173 train/test split  9.55   510  8.83
174 train/test split  8.57   511  7.69
175 train/test split  9.33   512  7.87
176 train/test split 10.2    518  9.01
177 train/test split  8.40   522  6.68
178 train/test split 12.4    529 11.5 
179 train/test split 12.3    534 11.9 
180 train/test split 12.6    538 13.2 
181 train/test split 11.1    540 10.0 
182 train/test split 11.8    541 10.9 
183 train/test split 11.1    542  9.43
184 train/test split 10.6    543  8.84
185 train/test split 12.3    548 11.9 
186 train/test split  6.49   552  6.22
187 train/test split  9.99   561  8.23
188 train/test split 11.7    564 11.7 
189 train/test split 11.5    569 11.9 
190 train/test split 11.6    573 13.2 
191 train/test split 10.3    574  9.67
192 train/test split 11.4    577 11.0 
193 train/test split 11.4    578 12.0 
194 train/test split 11.4    585  9.31
195 train/test split 11.7    587 12.7 
196 train/test split 11.2    588 11.1 
197 train/test split 11.8    592 11.4 
198 train/test split 11.6    595 12.3 
199 train/test split 12.0    596 12.4 
200 train/test split 11.5    597 11.6 
201 train/test split 11.7    598 12.5 
202 train/test split 10.3    599 11.7 
203 train/test split 10.5    600 10.5 
204 train/test split 11.6    601 10.5 
205 train/test split 10.8    602 11.6 
206 train/test split 12.1    605 13.1 
207 train/test split 10.6    607  9.86
208 train/test split 11.3    608 11.0 
209 train/test split 11.7    613 12.9 
210 train/test split 11.2    623 10.7 
211 train/test split 12.1    627 11.7 
212 train/test split 12.9    629 10.8 
213 train/test split 13.1    634 11.9 
214 train/test split 11.8    638 11.6 
215 train/test split 13.5    642 14.4 
216 train/test split 12.6    643 13.3 
217 train/test split 13.0    645 14.5 
218 train/test split 13.2    646 14.5 
219 train/test split 12.9    649 13.2 
220 train/test split 11.7    658 12.2 
221 train/test split 11.6    659 12.0 
222 train/test split 12.0    660 12.0 
223 train/test split 12.7    661 13.4 
224 train/test split 12.2    665 13.2 
225 train/test split 12.4    666 12.0 
226 train/test split  9.41   667  9.68
227 train/test split 11.4    670 10.3 
228 train/test split 11.5    675 11.7 
229 train/test split 11.4    676 11.4 
230 train/test split  8.46   678 11.7 
231 train/test split  8.32   682 13.3 
232 train/test split  8.50   687 13.1 
233 train/test split 12.9    696 17.1 
234 train/test split 12.4    697 10.8 
235 train/test split 12.6    701 13.9 
236 train/test split 11.7    707 13.0 
237 train/test split 12.7    708 13.2 
238 train/test split 13.1    719 13.4 
239 train/test split  9.51   726  6.61
240 train/test split 10.0    727  9.24
241 train/test split 11.2    733 11.1 
242 train/test split 12.3    736 12.6 
243 train/test split 11.5    737 12.2 
244 train/test split 10.0    740  9.10
245 train/test split 11.3    743 11.8 
246 train/test split 11.6    744 12.0 
247 train/test split  7.77   747  9.59
248 train/test split 11.8    754 12.3 
249 train/test split 10.4    757  9.95
250 train/test split 10.7    758  8.71
251 train/test split  7.93   760  5.68
252 train/test split 11.9    763 10.8 
253 train/test split 10.6    773 10.2 
254 train/test split 10.7    775 12.0 
255 train/test split 11.4    776 11.0 
256 train/test split 11.1    777  8.94
257 train/test split 11.9    780 10.3 
258 train/test split 10.3    782  8.87
259 train/test split  8.42   784  8.20
260 train/test split  9.60   787 10.6 
261 train/test split  9.37   794  7.90
262 train/test split  9.35   795  8.23
263 train/test split  9.50   796  9.86
264 train/test split  8.98   798  7.33
265 train/test split 12.0    803 12.0 
266 train/test split 11.6    806 11.1 
267 train/test split 11.6    808 11.8 
268 train/test split 11.8    810 10.8 
269 train/test split 11.3    811 10.5 
270 train/test split 11.9    816 11.6 
271 train/test split 11.1    817 10.2 
272 train/test split  9.33   822  8.39
273 train/test split  8.61   826  7.35
274 train/test split  9.23   827  8.40
275 train/test split 13.0    832 14.2 
276 train/test split 13.2    833 14.3 
277 train/test split 12.0    835 12.6 
278 train/test split 12.5    840 12.6 
279 train/test split 12.1    841 12.8 
280 train/test split  9.24   844  6.38
281 train/test split 11.4    846 12.4 
282 train/test split 10.8    847 10.6 
283 train/test split  9.00   848  6.96
284 train/test split 10.4    849 12.7 
285 train/test split 13.0    853 12.8 
286 train/test split 10.9    860 10.8 
287 train/test split  8.25   862 10.2 
288 train/test split  7.27   865  5.25
289 train/test split  6.08   866  5.85
290 train/test split  6.03   868  3.50
291 train/test split  6.69   871  7.05
292 train/test split  5.89   872  7.82

Nice!

Data Visualization


Our main question for this case study was:

Can we predict annual average air pollution concentrations at the granularity of zip code regional levels using predictors such as data about population density, urbanization, road density, as well as, satellite pollution data and chemical modeling data?

Thus far, we have build a machine learning (ML) model to predict fine particulate matter air pollution levels based on our predictor variables (or features).

Now, let’s make a plot of our predicted outcome values (\(\hat{Y}\)) and actual outcome values \(Y\) we observed.

First, let’s start by making a plot of our monitors. To do this, we will use the following packages to create a map of the US:

  1. sf - the simple features package helps to convert geographical coordinates into geometry variables which are useful for making 2D plots
  2. maps - this package contains geographical outlines and plotting functions to create plots with maps
  3. rnaturalearth- this allows for easy interaction with map data from Natural Earth which is a public domain map dataset
  4. rgeos - this package interfaces with the Geometry Engine-Open Source (GEOS) which is also helpful for coordinate conversion

We will start with getting an outline of the US with the ne_countries() function of the rnaturalearth package which will return polygons of the countries in the Natural Earth dataset.

AVOCADO: So I definitely have the rnaturalearthdata package installed, but for someone reason every time I go to run the code below it says that I need to install the package. Just curious – what version of R are you using?avocado response - the end of the case study shows this.. hopefully this works now :/ please let me know and I will figure it out

Rows: 241
Columns: 64
$ scalerank  <int> 3, 1, 1, 1, 1, 3, 3, 1, 1, 1, 3, 1, 5, 3, 1, 1, 1, 1, 1, 1…
$ featurecla <chr> "Admin-0 country", "Admin-0 country", "Admin-0 country", "…
$ labelrank  <dbl> 5, 3, 3, 6, 6, 6, 6, 4, 2, 6, 4, 4, 5, 6, 6, 2, 4, 5, 6, 2…
$ sovereignt <chr> "Netherlands", "Afghanistan", "Angola", "United Kingdom", …
$ sov_a3     <chr> "NL1", "AFG", "AGO", "GB1", "ALB", "FI1", "AND", "ARE", "A…
$ adm0_dif   <dbl> 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0…
$ level      <dbl> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2…
$ type       <chr> "Country", "Sovereign country", "Sovereign country", "Depe…
$ admin      <chr> "Aruba", "Afghanistan", "Angola", "Anguilla", "Albania", "…
$ adm0_a3    <chr> "ABW", "AFG", "AGO", "AIA", "ALB", "ALD", "AND", "ARE", "A…
$ geou_dif   <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ geounit    <chr> "Aruba", "Afghanistan", "Angola", "Anguilla", "Albania", "…
$ gu_a3      <chr> "ABW", "AFG", "AGO", "AIA", "ALB", "ALD", "AND", "ARE", "A…
$ su_dif     <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ subunit    <chr> "Aruba", "Afghanistan", "Angola", "Anguilla", "Albania", "…
$ su_a3      <chr> "ABW", "AFG", "AGO", "AIA", "ALB", "ALD", "AND", "ARE", "A…
$ brk_diff   <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ name       <chr> "Aruba", "Afghanistan", "Angola", "Anguilla", "Albania", "…
$ name_long  <chr> "Aruba", "Afghanistan", "Angola", "Anguilla", "Albania", "…
$ brk_a3     <chr> "ABW", "AFG", "AGO", "AIA", "ALB", "ALD", "AND", "ARE", "A…
$ brk_name   <chr> "Aruba", "Afghanistan", "Angola", "Anguilla", "Albania", "…
$ brk_group  <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ abbrev     <chr> "Aruba", "Afg.", "Ang.", "Ang.", "Alb.", "Aland", "And.", …
$ postal     <chr> "AW", "AF", "AO", "AI", "AL", "AI", "AND", "AE", "AR", "AR…
$ formal_en  <chr> "Aruba", "Islamic State of Afghanistan", "People's Republi…
$ formal_fr  <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ note_adm0  <chr> "Neth.", NA, NA, "U.K.", NA, "Fin.", NA, NA, NA, NA, "U.S.…
$ note_brk   <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "Multiple clai…
$ name_sort  <chr> "Aruba", "Afghanistan", "Angola", "Anguilla", "Albania", "…
$ name_alt   <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ mapcolor7  <dbl> 4, 5, 3, 6, 1, 4, 1, 2, 3, 3, 4, 4, 1, 7, 2, 1, 3, 1, 2, 3…
$ mapcolor8  <dbl> 2, 6, 2, 6, 4, 1, 4, 1, 1, 1, 5, 5, 2, 5, 2, 2, 1, 6, 2, 2…
$ mapcolor9  <dbl> 2, 8, 6, 6, 1, 4, 1, 3, 3, 2, 1, 1, 2, 9, 5, 2, 3, 5, 5, 1…
$ mapcolor13 <dbl> 9, 7, 1, 3, 6, 6, 8, 3, 13, 10, 1, NA, 7, 11, 5, 7, 4, 8, …
$ pop_est    <dbl> 103065, 28400000, 12799293, 14436, 3639453, 27153, 83888, …
$ gdp_md_est <dbl> 2258.0, 22270.0, 110300.0, 108.9, 21810.0, 1563.0, 3660.0,…
$ pop_year   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ lastcensus <dbl> 2010, 1979, 1970, NA, 2001, NA, 1989, 2010, 2010, 2001, 20…
$ gdp_year   <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ economy    <chr> "6. Developing region", "7. Least developed region", "7. L…
$ income_grp <chr> "2. High income: nonOECD", "5. Low income", "3. Upper midd…
$ wikipedia  <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ fips_10    <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ iso_a2     <chr> "AW", "AF", "AO", "AI", "AL", "AX", "AD", "AE", "AR", "AM"…
$ iso_a3     <chr> "ABW", "AFG", "AGO", "AIA", "ALB", "ALA", "AND", "ARE", "A…
$ iso_n3     <chr> "533", "004", "024", "660", "008", "248", "020", "784", "0…
$ un_a3      <chr> "533", "004", "024", "660", "008", "248", "020", "784", "0…
$ wb_a2      <chr> "AW", "AF", "AO", NA, "AL", NA, "AD", "AE", "AR", "AM", "A…
$ wb_a3      <chr> "ABW", "AFG", "AGO", NA, "ALB", NA, "ADO", "ARE", "ARG", "…
$ woe_id     <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ adm0_a3_is <chr> "ABW", "AFG", "AGO", "AIA", "ALB", "ALA", "AND", "ARE", "A…
$ adm0_a3_us <chr> "ABW", "AFG", "AGO", "AIA", "ALB", "ALD", "AND", "ARE", "A…
$ adm0_a3_un <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ adm0_a3_wb <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ continent  <chr> "North America", "Asia", "Africa", "North America", "Europ…
$ region_un  <chr> "Americas", "Asia", "Africa", "Americas", "Europe", "Europ…
$ subregion  <chr> "Caribbean", "Southern Asia", "Middle Africa", "Caribbean"…
$ region_wb  <chr> "Latin America & Caribbean", "South Asia", "Sub-Saharan Af…
$ name_len   <dbl> 5, 11, 6, 8, 7, 5, 7, 20, 9, 7, 14, 10, 23, 22, 17, 9, 7, …
$ long_len   <dbl> 5, 11, 6, 8, 7, 13, 7, 20, 9, 7, 14, 10, 27, 35, 19, 9, 7,…
$ abbrev_len <dbl> 5, 4, 4, 4, 4, 5, 4, 6, 4, 4, 9, 4, 7, 10, 6, 4, 5, 4, 4, …
$ tiny       <dbl> 4, NA, NA, NA, NA, 5, 5, NA, NA, NA, 3, NA, NA, 2, 4, NA, …
$ homepart   <dbl> NA, 1, 1, NA, 1, NA, 1, 1, 1, 1, NA, 1, NA, NA, 1, 1, 1, 1…
$ geometry   <MULTIPOLYGON [°]> MULTIPOLYGON (((-69.89912 1..., MULTIPOLYGON …

Here you can see the data about the countries in the world. Notice the geometry variable. This is used to create the outlines that we want.

Now we can use the geom_sf() function of the ggplot2 package to create a visual of simple feature (the geometry coordinates found in the geometry variable).

So now we can see that we have outlines of all the countries in the world.

We want to limit this just to the coordinates for the US. We will do this based on the coordinates we found on Wikipedia. According to this link, these are the latitude and longitude bounds of the continental US:

  • top = 49.3457868 # north lat
  • left = -124.7844079 # west long
  • right = -66.9513812 # east long
  • bottom = 24.7433195 # south lat

Now we just have a plot that is mostly limited to the outline of the US.

Now we will use the geom_point() function of the ggplot package to add scatter plot on top of the map. We want to show where the monitors are located based on the latitude and longitude values in the data.

Nice!

Now let’s add county lines.

County graphical data is available from the maps package. The sf package which again is short for simple features creates a data frame about this graphical data so that we can work with it.

Simple feature collection with 3076 features and 1 field
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -124.6813 ymin: 25.12993 xmax: -67.00742 ymax: 49.38323
CRS:            EPSG:4326
First 10 features:
                 ID                           geom
1   alabama,autauga MULTIPOLYGON (((-86.50517 3...
2   alabama,baldwin MULTIPOLYGON (((-87.93757 3...
3   alabama,barbour MULTIPOLYGON (((-85.42801 3...
4      alabama,bibb MULTIPOLYGON (((-87.02083 3...
5    alabama,blount MULTIPOLYGON (((-86.9578 33...
6   alabama,bullock MULTIPOLYGON (((-85.66866 3...
7    alabama,butler MULTIPOLYGON (((-86.8604 31...
8   alabama,calhoun MULTIPOLYGON (((-85.74313 3...
9  alabama,chambers MULTIPOLYGON (((-85.59416 3...
10 alabama,cherokee MULTIPOLYGON (((-85.46812 3...

Now we will use this data within the geom_sf() function to add this to our plot. We will also add a title using the ggtitle() function, as well as remove axis ticks and titles using the theme() function of the ggplot2 package.

Great!

Now, let’s add a fill at the county-level for the true monitor values of air pollution.

First, we need to get the county map data that we just got and our air pollution data to have similarly formatted county names so that we can combine the datasets together.

We can see that in the county data the counties are listed after the state name and a comma. In addition they are all lower case.

Simple feature collection with 6 features and 1 field
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -88.01778 ymin: 30.24071 xmax: -85.06131 ymax: 34.2686
CRS:            EPSG:4326
               ID                           geom
1 alabama,autauga MULTIPOLYGON (((-86.50517 3...
2 alabama,baldwin MULTIPOLYGON (((-87.93757 3...
3 alabama,barbour MULTIPOLYGON (((-85.42801 3...
4    alabama,bibb MULTIPOLYGON (((-87.02083 3...
5  alabama,blount MULTIPOLYGON (((-86.9578 33...
6 alabama,bullock MULTIPOLYGON (((-85.66866 3...

In contrast, our air pollution pm data shows counties as titles with the first letter as upper case.

[1] "Baldwin" "Clay"    "Colbert" "DeKalb"  "Etowah"  "Houston"

We can use the separate() function of the tidyr package to separate the ID variable of our counties data into two variables based on the comma as a separator.

Simple feature collection with 6 features and 2 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -88.01778 ymin: 30.24071 xmax: -85.06131 ymax: 34.2686
CRS:            EPSG:4326
    state  county                           geom
1 alabama autauga MULTIPOLYGON (((-86.50517 3...
2 alabama baldwin MULTIPOLYGON (((-87.93757 3...
3 alabama barbour MULTIPOLYGON (((-85.42801 3...
4 alabama    bibb MULTIPOLYGON (((-87.02083 3...
5 alabama  blount MULTIPOLYGON (((-86.9578 33...
6 alabama bullock MULTIPOLYGON (((-85.66866 3...

Now we just need to make these names in the new county variable of the counties data to be in title format. We can use the str_to_title() function of the stringr package to do this.

Great! Now the county information is the same for the counties and pm data.

We can use the inner_join() function of the dplyr package to join the datasets together based on the county variables in each. This function will keep all rows that are in both datasets.

Rows: 3,926
Columns: 52
$ state.x                     <chr> "alabama", "alabama", "alabama", "alabama…
$ county                      <chr> "Baldwin", "Bibb", "Bibb", "Butler", "But…
$ id                          <fct> 1003.001, 13021.0007, 13021.0012, 39017.0…
$ value                       <dbl> 9.597647, 12.253134, 12.233673, 13.918079…
$ fips                        <fct> 1003, 13021, 13021, 39017, 39017, 13059, …
$ lat                         <dbl> 30.49800, 32.77746, 32.80541, 39.49380, 3…
$ lon                         <dbl> -87.88141, -83.64110, -83.54352, -84.3543…
$ state.y                     <chr> "Alabama", "Georgia", "Georgia", "Ohio", …
$ city                        <chr> "In a city", "In a city", "In a city", "I…
$ CMAQ                        <dbl> 8.098836, 11.716801, 11.716801, 11.321991…
$ zcta                        <fct> 36532, 31206, 31020, 45044, 45015, 30605,…
$ zcta_area                   <dbl> 190980522, 72325015, 276913325, 98746815,…
$ zcta_pop                    <dbl> 27829, 29072, 2541, 52822, 12038, 39952, …
$ imp_a500                    <dbl> 0.01730104, 35.64359862, 0.28200692, 27.4…
$ imp_a1000                   <dbl> 1.4096021, 24.4824827, 0.2973616, 28.2887…
$ imp_a5000                   <dbl> 3.3360118, 8.7317283, 2.4691097, 22.69740…
$ imp_a10000                  <dbl> 1.9879187, 9.1999720, 1.9873487, 11.90155…
$ imp_a15000                  <dbl> 1.4386207, 6.4619966, 3.6435089, 8.405292…
$ county_area                 <dbl> 4117521611, 646879637, 646879637, 1209668…
$ county_pop                  <dbl> 182265, 155547, 155547, 368130, 368130, 1…
$ log_dist_to_prisec          <dbl> 4.648181, 7.635438, 7.576493, 5.959728, 5…
$ log_pri_length_5000         <dbl> 8.517193, 10.215058, 10.659655, 9.747731,…
$ log_pri_length_10000        <dbl> 9.210340, 12.116408, 11.653566, 10.734382…
$ log_pri_length_15000        <dbl> 9.630228, 12.591833, 12.313347, 11.172817…
$ log_pri_length_25000        <dbl> 11.32735, 13.12475, 13.02383, 12.14238, 1…
$ log_prisec_length_500       <dbl> 7.295356, 6.214608, 6.214608, 8.104926, 7…
$ log_prisec_length_1000      <dbl> 8.195119, 7.600902, 7.600902, 9.180109, 8…
$ log_prisec_length_5000      <dbl> 10.81504, 11.61283, 11.34202, 11.63677, 1…
$ log_prisec_length_10000     <dbl> 11.88680, 13.23991, 12.47152, 12.62117, 1…
$ log_prisec_length_15000     <dbl> 12.20572, 13.74532, 13.37704, 13.26170, 1…
$ log_prisec_length_25000     <dbl> 13.41395, 14.28483, 14.25295, 14.16745, 1…
$ log_nei_2008_pm25_sum_10000 <dbl> 0.3180354, 5.5380414, 0.1972941, 7.230784…
$ log_nei_2008_pm25_sum_15000 <dbl> 1.967359, 5.538041, 5.536822, 7.408732, 5…
$ log_nei_2008_pm25_sum_25000 <dbl> 5.067308, 5.986616, 5.986572, 7.538614, 7…
$ log_nei_2008_pm10_sum_10000 <dbl> 1.3558851, 5.6035407, 0.9849611, 7.278576…
$ log_nei_2008_pm10_sum_15000 <dbl> 2.2678341, 5.6035407, 5.5971561, 7.489928…
$ log_nei_2008_pm10_sum_25000 <dbl> 5.6287284, 6.0302900, 6.0347672, 7.633004…
$ popdens_county              <dbl> 44.265706, 240.457407, 240.457407, 304.32…
$ popdens_zcta                <dbl> 145.716431, 401.963277, 9.176156, 534.923…
$ nohs                        <dbl> 3.300, 8.700, 6.800, 1.300, 3.500, 3.800,…
$ somehs                      <dbl> 4.900, 24.700, 17.500, 3.300, 7.700, 6.80…
$ hs                          <dbl> 25.10, 32.60, 46.60, 33.40, 33.40, 17.80,…
$ somecollege                 <dbl> 19.7, 12.9, 18.7, 23.0, 23.2, 16.1, 18.8,…
$ associate                   <dbl> 8.200, 4.800, 5.000, 8.700, 8.100, 5.100,…
$ bachelor                    <dbl> 25.30, 8.90, 4.00, 22.20, 16.20, 25.40, 5…
$ grad                        <dbl> 13.50, 7.30, 1.30, 8.10, 7.90, 25.00, 3.1…
$ pov                         <dbl> 6.100, 38.800, 26.800, 0.900, 6.900, 12.1…
$ hs_orless                   <dbl> 33.30, 66.00, 70.90, 38.00, 44.60, 28.40,…
$ urc2013                     <dbl> 4, 4, 4, 2, 2, 4, 6, 2, 4, 1, 1, 1, 3, 4,…
$ urc2006                     <dbl> 5, 4, 4, 2, 2, 4, 6, 2, 4, 1, 1, 1, 3, 4,…
$ aod                         <dbl> 37.36364, 36.25000, 30.45455, 48.36364, 5…
$ geom                        <MULTIPOLYGON [°]> MULTIPOLYGON (((-87.93757 3.…

Nice! we can see that we have add a geom variable to the pm data.

Now we can use this to color the counties in our plot based on the value variable of our pm data, which you may recall is the actual monitor data for fine particulate air pollution at each monitor.

WE can do so using the scale_fill_gradientn() function of the ggplot2 package which creates color gradient based on a variable. In this case it is the variable that was specified as the fill in the aes function of the geom_sf() function. We specified that it would be the value variable of the pm data.

This scale_fill_gradientn() function also allows you to specify the colors, what to do about NA values (should they be a specific color or transparent) and the breaks, limits, labels and name/title on the legend for the color gradient.

Nice!

Now let’s do the same with our predicted outcome values.

Let’s grab both the testing and training predicted outcome values so that we have as much data as possible.

First we need to fit our training data with our final model to be able to get the predictions for the monitors included in the training set. We did this using the last_fit() function, but the output of this makes it difficult to grab the predicted values for the training data, and it is also difficult to get the id variables for the testing data.

Thus we will use the parsnip fit() and predict() functions to do this outside of the workflows package like so:

Question Opportunity

Why do we not need pre-processed data?


Click here to reveal the answer.

Since we are using a workflow, the data will be pre-processed when it is fit as well.


# A tibble: 584 x 5
   .pred value fips  county    id       
   <dbl> <dbl> <fct> <chr>     <fct>    
 1  10.0  9.60 1003  Baldwin   1003.001 
 2  11.0 10.8  1027  Clay      1027.0001
 3  11.5 11.2  1033  Colbert   1033.1002
 4  12.0 12.4  1055  Etowah    1055.001 
 5  10.8 10.5  1069  Houston   1069.0003
 6  14.5 15.6  1073  Jefferson 1073.0023
 7  12.1 12.4  1073  Jefferson 1073.1005
 8  11.0 11.1  1073  Jefferson 1073.1009
 9  13.9 14.6  1073  Jefferson 1073.2003
10  11.8 12.0  1073  Jefferson 1073.5002
# … with 574 more rows
# A tibble: 292 x 5
   .pred value fips  county     id       
   <dbl> <dbl> <fct> <chr>      <fct>    
 1 11.7  11.7  1049  DeKalb     1049.1003
 2 12.6  13.1  1073  Jefferson  1073.101 
 3 12.3  12.2  1073  Jefferson  1073.2006
 4 12.0  12.2  1089  Madison    1089.0014
 5 11.5  11.4  1103  Morgan     1103.0011
 6 12.4  12.2  1121  Talladega  1121.0002
 7 10.8  10.9  4013  Maricopa   4013.4003
 8 10.4  10.6  4021  Pinal      4021.0001
 9 12.0  14.1  4023  Santa Cruz 4023.0004
10  7.69  5.83 4025  Yavapai    4025.2002
# … with 282 more rows

Now we can combine this data for the predictions for all monitors using the bind_rows() function of the dplyr package, which will essentially append the second dataset to the first.

# A tibble: 876 x 5
   .pred value fips  county     id       
   <dbl> <dbl> <fct> <chr>      <fct>    
 1 11.7  11.7  1049  DeKalb     1049.1003
 2 12.6  13.1  1073  Jefferson  1073.101 
 3 12.3  12.2  1073  Jefferson  1073.2006
 4 12.0  12.2  1089  Madison    1089.0014
 5 11.5  11.4  1103  Morgan     1103.0011
 6 12.4  12.2  1121  Talladega  1121.0002
 7 10.8  10.9  4013  Maricopa   4013.4003
 8 10.4  10.6  4021  Pinal      4021.0001
 9 12.0  14.1  4023  Santa Cruz 4023.0004
10  7.69  5.83 4025  Yavapai    4025.2002
# … with 866 more rows

Great! as we can see there are 876 values like we would expect for all of the monitors. We can use the county variable to combine this with the counties data like we did with the pm data previously so that we can use the value variable as a color scheme for our map.

Now we will use the patchwork package to combine our last two plots. This allows us to combine plots using the + or the / . The + will place plots side by side and the / will place plots top to bottom.

Now let’s just combine the truth plot and the prediction plots together:

We can see that the predicted fine particle air pollution values in (ug/m3) are quite similar to the true values measured by the actual gravimetric monitors. We can also see that southern California has some large counties with worse pollution (as they are yellow and thus have much higher particulate matter levels).

Let’s add some text to our plot to explain it a bit more.

Summary


Synopsis

In this case study, we explored gravimetric monitoring data of fine particulate matter air pollution (outcome variable). Our goal was to able to predict air pollution on monitors where we only had predictor variables (or features) without having observed a corresponding measurement of air pollution.

Our learning objectives were:

  • Introduce concepts in machine learning
  • Demonstrate how to build a machine learning model with tidymodels
  • Demonstrate how to visualize geo-spatial data using ggplot2

Using the machine learning models built in this case study, we could now extend this model to be used to predict air pollution levels in areas with poor monitoring, to help identify regions where populations maybe especially at risk for the health effects of air pollution.

Analyses like the one in our case study are important for defining which groups could benefit the most from interventions, education, and policy changes when attempting to mitigate public health challenges. You can see in this article that many additional considerations would be involved to adequately understand the data enough to recommend policy changes.

Click here for more on what we learned with tidymodels

Here, we provide an overview of the tidymodels framework. avocado… two options:

We performed the major steps of machine learning that we introduced in the beginning of the data analysis:

  1. Data exploration

We used packages like skimr, summarytools, corrplot, and GGally to better understand our data. These packages gave can tell us how many missing values each variable has (if any), the class of each variable, the distribution of values for each variable, the sparsity of each variable, and the level of correlation between variables.

  1. Data splitting

We used the rsample package to first perform an initial split of our data into two pieces: a training set and a testing set. The training set was used to optimize the model, while the testing set was used only to evaluate the performance of our final model. We also used the rsample package to create cross validation subsets of our training data. This allowed us to better assess the performance of our tested models using our training data.

  1. Variable assignment and pre-processing

We used the recipes package to assign variable roles (such as outcome, predictor, and id variable). We also used this package to create a recipe for pre-processing our training and testing data. This involved steps such as: step_dummy to create dummy numeric encodings of our categorical variables, step_corr to remove highly correlated variables, step_nzv to remove near zero variance variables that would contribute little to our model and potentially add noise. We learned that once our recipe was created and prepped using prep()we could extract the pre-processed training data using juice() or our pre-processed testing data using bake(). We also learned that if we used the newer workflows package that we did not need to the prep(), juice(), or bake() functions, but that it is still useful to know how to do so if we want to look at our data and how the recipe is influencing it more deeply.

  1. Model specification, fitting, tuning and performance evaluation using the training data

We learned that the model needs to first be fit to the training data. We learned that in both classification and prediction, the model is fit to the training data and the explanatory variables are used to estimate numeric values (in the case of prediction) or categorical values (in the case of classification) of the outcome variable of interest. We learned that we specify the model and its specifications using the parnsip package and that we also use this package to fit the model using the fit() function. We learned that we if just use parsnip to fit the model, then we need to use the pre-processed training data (output from juice()). We learned that we can use the raw training data if we use the workflows package to create a workflow that pre-processes our data for us.

We learned that if the model fits well than the estimated values will be very similar to the true outcome variable values in our training data. We learned that we can assess model performance using the yardstick package with the metrics functio or the tune package and the collect_metrics() function (required if using cross validation or tuning). We also learned that we can use subsets of our training data (which we created with the rsample package) to perform cross validation to get a better estimate about the performance of our model using our training data, as we want our results to be generalizable and to perform well with other data, not just our training data. We used the fit_resamples() function of the tune package to fit our model on our different training data subsets and the collect_metrics() function (also of the tune package) to evaluate model performance using these subsets. We also learned that we can potentially improve model performance by tuning aspects about the model called hyperparameters to determine the best option for model performance. We learned that we can do this using the tune and dials packages and evaluating the performance of our model with the different hyperparameter options and our training data subsets that we used for cross validation. After we tested several different methods to model our data, we compared them to choose the best performing model as our final model.

  1. Overall model performance evaluation

Once we chose our final model, we evaluated the final model performance using the testing data using the last_fit() function of the tune package. This gives us a better estimate about how well the model will predict or classify the outcome variable of interest with new independent data. Ideally one would also perform an evaluation with independent data to provide a sense of how generalizable the model is to other data sources.

We also saw that we can use the collect_predictions() function of the tune package to get the predictions for our test data. We saw that we can get more detailed prediction data using the predict() function of the parsnip package.

Suggested Homework

Students can predict air pollution monitor values using a different algorithm and provide an explanation for how that algorithm works and why it may be a good choice for modeling this data.

Additional Information


Session info

R version 4.0.1 (2020-06-06)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Mojave 10.14.5

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRblas.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] parallel  stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
 [1] patchwork_1.0.0     rgeos_0.5-3         sp_1.4-2           
 [4] rnaturalearth_0.1.0 maps_3.3.0          sf_0.9-3           
 [7] lwgeom_0.2-4        tidyr_1.1.0         stringr_1.4.0      
[10] doParallel_1.0.15   iterators_1.0.12    foreach_1.5.0      
[13] randomForest_4.6-14 vip_0.2.2           yardstick_0.0.6    
[16] workflows_0.1.1     tune_0.1.0          tibble_3.0.1       
[19] rsample_0.0.7       recipes_0.1.12      purrr_0.3.4        
[22] parsnip_0.1.1       infer_0.5.1         dials_0.0.7        
[25] scales_1.1.1        broom_0.5.6         tidymodels_0.1.0   
[28] GGally_2.0.0        ggplot2_3.3.1       RColorBrewer_1.1-2 
[31] corrplot_0.84       magrittr_1.5        summarytools_0.9.6 
[34] skimr_2.1.1         dplyr_1.0.0         readr_1.3.1        
[37] here_0.1            knitr_1.28         

loaded via a namespace (and not attached):
  [1] utf8_1.1.4              tidyselect_1.1.0        lme4_1.1-23            
  [4] htmlwidgets_1.5.1       grid_4.0.1              pROC_1.16.2            
  [7] munsell_0.5.0           codetools_0.2-16        units_0.6-6            
 [10] statmod_1.4.34          DT_0.14                 future_1.17.0          
 [13] miniUI_0.1.1.1          withr_2.2.0             colorspace_1.4-1       
 [16] highr_0.8               rstudioapi_0.11         stats4_4.0.1           
 [19] bayesplot_1.7.2         listenv_0.8.0           labeling_0.3           
 [22] rstan_2.19.3            repr_1.1.0              rnaturalearthdata_0.1.0
 [25] farver_2.0.3            DiceDesign_1.8-1        rprojroot_1.3-2        
 [28] vctrs_0.3.1             generics_0.0.2          ipred_0.9-9            
 [31] xfun_0.14               R6_2.4.1                markdown_1.1           
 [34] rstanarm_2.19.3         lhs_1.0.2               reshape_0.8.8          
 [37] assertthat_0.2.1        promises_1.1.1          nnet_7.3-14            
 [40] gtable_0.3.0            globals_0.12.5          processx_3.4.2         
 [43] timeDate_3043.102       rlang_0.4.6             splines_4.0.1          
 [46] rapportools_1.0         checkmate_2.0.0         inline_0.3.15          
 [49] yaml_2.2.1              reshape2_1.4.4          tidytext_0.2.4         
 [52] threejs_0.3.3           crosstalk_1.1.0.1       backports_1.1.7        
 [55] httpuv_1.5.4            rsconnect_0.8.16        tokenizers_0.2.1       
 [58] tools_4.0.1             lava_1.6.7              tcltk_4.0.1            
 [61] ellipsis_0.3.1          ggridges_0.5.2          Rcpp_1.0.4.6           
 [64] plyr_1.8.6              base64enc_0.1-3         classInt_0.4-3         
 [67] ps_1.3.3                prettyunits_1.1.1       rpart_4.1-15           
 [70] zoo_1.8-8               furrr_0.1.0             magick_2.3             
 [73] colourpicker_1.0        GPfit_1.0-8             SnowballC_0.7.0        
 [76] matrixStats_0.56.0      tidyposterior_0.0.3     hms_0.5.3              
 [79] shinyjs_1.1             mime_0.9                evaluate_0.14          
 [82] xtable_1.8-4            tidypredict_0.4.5       shinystan_2.5.0        
 [85] gridExtra_2.3           rstantools_2.1.0        compiler_4.0.1         
 [88] KernSmooth_2.23-17      crayon_1.3.4            minqa_1.2.4            
 [91] StanHeaders_2.21.0-3    htmltools_0.4.0         later_1.1.0.1          
 [94] lubridate_1.7.8         DBI_1.1.0               MASS_7.3-51.6          
 [97] boot_1.3-25             Matrix_1.2-18           cli_2.0.2              
[100] pryr_0.1.4              gower_0.2.1             igraph_1.2.5           
[103] pkgconfig_2.0.3         dygraphs_1.1.1.6        hardhat_0.1.3          
[106] prodlim_2019.11.13      janeaustenr_0.1.5       callr_3.4.3            
[109] digest_0.6.25           rmarkdown_2.2           shiny_1.4.0.2          
[112] gtools_3.8.2            nloptr_1.2.2.1          lifecycle_0.2.0        
[115] nlme_3.1-148            jsonlite_1.7.1          fansi_0.4.1            
[118] pillar_1.4.4            lattice_0.20-41         loo_2.2.0              
[121] fastmap_1.0.1           pkgbuild_1.0.8          survival_3.1-12        
[124] glue_1.4.1              xts_0.12-0              shinythemes_1.1.2      
[127] pander_0.6.3            class_7.3-17            stringi_1.4.6          
[130] e1071_1.7-3            

Acknowledgements

We would like to acknowledge Roger Peng, Megan Latshaw, and Kristen Koehlerfor assisting in framing the major direction of the case study.

We would also like to acknowledge the Bloomberg American Health Initiative for funding this work.

LS0tCnRpdGxlOiAiT3BlbiBDYXNlIFN0dWRpZXM6IFByZWRpY3RpbmcgQW5udWFsIEFpciBQb2xsdXRpb24gIgpjc3M6IHN0eWxlLmNzcwpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHNlbGZfY29udGFpbmVkOiB5ZXMKICAgIGNvZGVfZG93bmxvYWQ6IHllcwogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgbnVtYmVyX3NlY3Rpb25zOiBubwogICAgdGhlbWU6IGNvc21vCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICBwZGZfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogIHdvcmRfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwoKLS0tCjxzdHlsZT4KI1RPQyB7CiAgYmFja2dyb3VuZDogdXJsKCJodHRwczovL29wZW5jYXNlc3R1ZGllcy5naXRodWIuaW8vaW1nL2xvZ28uanBnIik7CiAgYmFja2dyb3VuZC1zaXplOiBjb250YWluOwogIHBhZGRpbmctdG9wOiAyNDBweCAhaW1wb3J0YW50OwogIGJhY2tncm91bmQtcmVwZWF0OiBuby1yZXBlYXQ7Cn0KPC9zdHlsZT4KCjwhLS0gT3BlbiBhbGwgbGlua3MgaW4gbmV3IHRhYi0tPiAgCjxiYXNlIHRhcmdldD0iX2JsYW5rIi8+IAoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoaGVyZSkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGluY2x1ZGUgPSBUUlVFLCBjb21tZW50ID0gTkEsIGVjaG8gPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIGNhY2hlID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICBmaWcuYWxpZ24gPSAiY2VudGVyIiwgb3V0LndpZHRoID0gJzkwJScpCmBgYAoKCiMjIyMgey5vdXRsaW5lIH0KYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjgwMCBweCJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsICJtYWluX3Bsb3RfbWFwcy5wbmciKSkKYGBgCgojIyMjCgojIyB7LmRpc2NsYWltZXJfYmxvY2t9CgoqKkRpc2NsYWltZXIqKjogVGhlIHB1cnBvc2Ugb2YgdGhlIFtPcGVuIENhc2UgU3R1ZGllc10oaHR0cHM6Ly9vcGVuY2FzZXN0dWRpZXMuZ2l0aHViLmlvKXt0YXJnZXQ9Il9ibGFuayJ9IHByb2plY3QgaXMgKip0byBkZW1vbnN0cmF0ZSB0aGUgdXNlIG9mIHZhcmlvdXMgZGF0YSBzY2llbmNlIG1ldGhvZHMsIHRvb2xzLCBhbmQgc29mdHdhcmUgaW4gdGhlIGNvbnRleHQgb2YgbWVzc3ksIHJlYWwtd29ybGQgZGF0YSoqLiAKQSBnaXZlbiBjYXNlIHN0dWR5IGRvZXMgbm90IGNvdmVyIGFsbCBhc3BlY3RzIG9mIHRoZSByZXNlYXJjaCBwcm9jZXNzLCBpcyBub3QgY2xhaW1pbmcgdG8gYmUgdGhlIG1vc3QgYXBwcm9wcmlhdGUgd2F5IHRvIGFuYWx5emUgYSBnaXZlbiBkYXRhIHNldCwgYW5kIHNob3VsZCBub3QgYmUgdXNlZCBpbiB0aGUgY29udGV4dCBvZiBtYWtpbmcgcG9saWN5IGRlY2lzaW9ucyB3aXRob3V0IGV4dGVybmFsIGNvbnN1bHRhdGlvbiBmcm9tIHNjaWVudGlmaWMgZXhwZXJ0cy4gCgojIyMjIHsubGljZW5zZV9ibG9ja30KClRoaXMgd29yayBpcyBsaWNlbnNlZCB1bmRlciB0aGUgQ3JlYXRpdmUgQ29tbW9ucyBBdHRyaWJ1dGlvbi1Ob25Db21tZXJjaWFsIDMuMCBbKENDIEJZLU5DIDMuMCldKGh0dHBzOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS1uYy8zLjAvdXMvKXt0YXJnZXQ9Il9ibGFuayJ9IFVuaXRlZCBTdGF0ZXMgTGljZW5zZS4KCiMjIyMKCgojICoqTW90aXZhdGlvbioqCioqKgpBIHZhcmlldHkgb2YgZGlmZmVyZW50IHNvdXJjZXMgY29udHJpYnV0ZSBkaWZmZXJlbnQgdHlwZXMgb2YgcG9sbHV0YW50cyB0byB3aGF0IHdlIGNhbGwgYWlyIHBvbGx1dGlvbi4gCgpTb21lIHNvdXJjZXMgYXJlIG5hdHVyYWwgd2hpbGUgb3RoZXJzIGFyZSBhbnRocm9wb2dlbmljIChodW1hbiBkZXJpdmVkKToKCjxwIGFsaWduPSJjZW50ZXIiPgo8aW1nIHdpZHRoPSI2MDAiIHNyYz0iaHR0cHM6Ly93d3cubnBzLmdvdi9zdWJqZWN0cy9haXIvaW1hZ2VzL1NvdXJjZXNfR3JhcGhpY19IdWdlLmpwZz9tYXh3aWR0aD0xMjAwJm1heGhlaWdodD0xMjAwJmF1dG9yb3RhdGU9ZmFsc2UiPgo8L3A+CgojIyMjIyBbW3NvdXJjZV1dKGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vdXJsP3NhPWkmdXJsPWh0dHBzJTNBJTJGJTJGd3d3Lm5wcy5nb3YlMkZzdWJqZWN0cyUyRmFpciUyRnNvdXJjZXMuaHRtJnBzaWc9QU92VmF3MnY3QVZ4U0Y4WlNBUEVoTnVkVnRiTiZ1c3Q9MTU4NTc3MDk2NjIxNzAwMCZzb3VyY2U9aW1hZ2VzJmNkPXZmZSZ2ZWQ9MENBSVFqUnhxRndvVENQRE42NnFfeGVnQ0ZRQUFBQUFkQUFBQUFCQUQpe3RhcmdldD0iX2JsYW5rIn0KCiMjIyBNYWpvciB0eXBlcyBvZiBhaXIgcG9sbHV0YW50cwoKMSkgKipHYXNlb3VzKiogLSBDYXJib24gTW9ub3hpZGUgKENPKSwgT3pvbmUgKE9+M34pLCBOaXRyb2dlbiBPeGlkZXMoTk8sIE5PfjJ+KSwgU3VscGhlciBEaW94aWRlIChTT34yfikKMikgKipQYXJ0aWN1bGF0ZSoqIC0gc21hbGwgbGlxdWlkcyBhbmQgc29saWRzIHN1c3BlbmRlZCBpbiB0aGUgYWlyIChpbmNsdWRlcyBsZWFkLSBjYW4gaW5jbHVkZSBjZXJ0YWluIHR5cGVzIG9mIGR1c3QpCjMpICoqRHVzdCoqIC0gc21hbGwgc29saWRzIChsYXJnZXIgdGhhbiBwYXJ0aWN1bGF0ZXMpIHRoYXQgY2FuIGJlIHN1c3BlbmRlZCBpbiB0aGUgYWlyIGZvciBzb21lIHRpbWUgYnV0IGV2ZW50dWFsbHkgc2V0dGxlCjQpICoqQmlvbG9naWNhbCoqIC0gcG9sbGVuLCBiYWN0ZXJpYSwgdmlydXNlcywgbW9sZCBzcG9yZXMKClNlZSBbaGVyZV0oaHR0cDovL3d3dy5yZWRsb2dlbnYuY29tL3dvcmtlci1zYWZldHkvcGFydC0xLWR1c3QtYW5kLXBhcnRpY3VsYXRlLW1hdHRlcikgZm9yIG1vcmUgZGV0YWlsIG9uIHRoZSB0eXBlcyBvZiBwb2xsdXRhbnRzIGluIHRoZSBhaXIuCgoKIyMjIFBhcnRpY3VsYXRlIHBvbGx1dGlvbiAKCkFpciBwb2xsdXRpb24gcGFydGljdWxhdGVzIGFyZSBnZW5lcmFsbHkgZGVzY3JpYmVkIGJ5IHRoZWlyICoqc2l6ZSoqLgoKVGhlcmUgYXJlIDMgbWFqb3IgY2F0ZWdvcmllczoKCjEpICoqTGFyZ2UgQ29hcnNlKiogUGFydGljdWxhdGUgTWF0ZXIgLSBoYXMgZGlhbWV0ZXIgb2YgPjEwIG1pY3JvbWV0ZXJzICgxMCDCtW0pIAoKMikgKipDb2Fyc2UqKiBQYXJ0aWN1bGF0ZSBNYXRlciAoY2FsbGVkICoqUE1+MTAtMi41fioqKSAtIGhhcyBkaWFtZXRlciBvZiBiZXR3ZWVuIDIuNSDCtW0gYW5kIDEwIMK1bQoKMykgKipGaW5lKiogUGFydGljdWxhdGUgTWF0ZXIgKGNhbGxlZCAqKlBNfjIuNX4qKikgLSBoYXMgZGlhbWV0ZXIgb2YgPCAyLjUgwrVtIAoKKipQTX4xMH4qKiBpbmNsdWRlcyBhbnkgcGFydGljdWxhdGUgbWF0ZXIgPDEwIMK1bSAoYm90aCBjb2Fyc2UgYW5kIGZpbmUgcGFydGljdWxhdGUgbWF0ZXIpCgpIZXJlIHlvdSBjYW4gc2VlIGhvdyB0aGVzZSBzaXplcyBjb21wYXJlIHdpdGggYSBodW1hbiBoYWlyOgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoPSAiNjAwIHB4In0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwgInBtMi41X3NjYWxlX2dyYXBoaWMtY29sb3JfMi5qcGciKSkKYGBgCgojIyMjIyBbW3NvdXJjZV1dKGh0dHBzOi8vd3d3LmVwYS5nb3YvcG0tcG9sbHV0aW9uL3BhcnRpY3VsYXRlLW1hdHRlci1wbS1iYXNpY3Mpe3RhcmdldD0iX2JsYW5rIn0KCjwhLS0gPHAgYWxpZ249ImNlbnRlciI+IC0tPgo8IS0tICAgPGltZyB3aWR0aD0iNTAwIiBzcmM9Imh0dHBzOi8vd3d3LnNlbnNpcmlvbi5jb20vaW1hZ2VzL3NlbnNpcmlvbi1zcGVjaWFsaXN0LWFydGljbGUtZmlndXJlLTEtY2RkNzAuanBnIj4gLS0+CjwhLS0gPC9wPiAtLT4KCgo8dT5UaGUgZm9sbG93aW5nIHBsb3QgYW5kIHRhYmxlIHNob3cgdGhlIHJlbGF0aXZlIHNpemVzIG9mIHRoZXNlIGRpZmZlcmVudCBwb2xsdXRhbnRzIGluIG1pY3JvbWV0ZXJzICjCtW0pOjwvdT4KCjxwIGFsaWduPSJjZW50ZXIiPgogIDxpbWcgd2lkdGg9IjYwMCIgc3JjPSJodHRwczovL3VwbG9hZC53aWtpbWVkaWEub3JnL3dpa2lwZWRpYS9jb21tb25zL3RodW1iL2QvZGYvQWlyYm9ybmUtcGFydGljdWxhdGUtc2l6ZS1jaGFydC5zdmcvODAwcHgtQWlyYm9ybmUtcGFydGljdWxhdGUtc2l6ZS1jaGFydC5zdmcucG5nIj4KPC9wPgoKIyMjIyMgW1tzb3VyY2VdXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9QYXJ0aWN1bGF0ZXMpe3RhcmdldD0iX2JsYW5rIn0KCgo8cCBhbGlnbj0iY2VudGVyIj4KICA8aW1nIHdpZHRoPSI1MDAiIHNyYz0iaHR0cHM6Ly93d3cuZnJvbnRpZXJzaW4ub3JnL2ZpbGVzL0FydGljbGVzLzUwNTU3MC9mcHViaC0wOC0wMDAxNC1IVE1ML2ltYWdlX20vZnB1YmgtMDgtMDAwMTQtdDAwMi5qcGciPgo8L3A+CgojIyMjIyBbW3NvdXJjZV1dKGh0dHBzOi8vd3d3LmZyb250aWVyc2luLm9yZy9hcnRpY2xlcy8xMC4zMzg5L2ZwdWJoLjIwMjAuMDAwMTQvZnVsbCl7dGFyZ2V0PSJfYmxhbmsifQoKCjx1PlRoaXMgdGFibGUgc2hvd3MgaG93IGRlZXBseSBzb21lIG9mIHRoZSBzbWFsbGVyIGZpbmUgcGFydGljbGVzIGNhbiBwZW5ldHJhdGUgd2l0aGluIHRoZSBodW1hbiBib2R5OjwvdT4KCjxwIGFsaWduPSJjZW50ZXIiPgogIDxpbWcgd2lkdGg9IjUwMCIgc3JjPSJodHRwczovL3d3dy5mcm9udGllcnNpbi5vcmcvZmlsZXMvQXJ0aWNsZXMvNTA1NTcwL2ZwdWJoLTA4LTAwMDE0LUhUTUwvaW1hZ2VfbS9mcHViaC0wOC0wMDAxNC10MDAxLmpwZyI+CjwvcD4KCiMjIyMjIFtbc291cmNlXV0oaHR0cHM6Ly93d3cuZnJvbnRpZXJzaW4ub3JnL2FydGljbGVzLzEwLjMzODkvZnB1YmguMjAyMC4wMDAxNC9mdWxsKXt0YXJnZXQ9Il9ibGFuayJ9CgoKIyMjIE5lZ2F0aXZlIGltcGFjdCBvZiBwYXJ0aWN1bGF0ZSBleHBvc3VyZSBvbiBoZWFsdGggCgpFeHBvc3VyZSB0byBhaXIgcG9sbHV0aW9uIGlzIGFzc29jaWF0ZWQgd2l0aCBoaWdoZXIgcmF0ZXMgb2YgW21vcnRhbGl0eV0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNTc4MzE4Ni8pe3RhcmdldD0iX2JsYW5rIn0gaW4gb2xkZXIgYWR1bHRzIGFuZCBpcyBrbm93biB0byBiZSBhIHJpc2sgZmFjdG9yIGZvciBtYW55IGRpc2Vhc2VzIGFuZCBjb25kaXRpb25zIGluY2x1ZGluZyBidXQgbm90IGxpbWl0ZWQgdG86CgoxKSBbQXN0aG1hXShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3B1Ym1lZC8yOTI0MzkzNyl7dGFyZ2V0PSJfYmxhbmsifSAtIGZpbmUgcGFydGljbGUgZXhwb3N1cmUgKCoqUE1+Mi41fioqKSB3YXMgZm91bmQgdG8gYmUgYXNzb2NpYXRlZCB3aXRoIGhpZ2hlciByYXRlcyBvZiBhc3RobWEgaW4gY2hpbGRyZW4KMikgW0luZmxhbW1hdGlvbiBpbiB0eXBlIDEgZGlhYmV0ZXNdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcHVibWVkLzMxNDE5NzY1KXt0YXJnZXQ9Il9ibGFuayJ9IC0gZmluZSBwYXJ0aWNsZSBleHBvc3VyZSAoKipQTX4yLjV+KiopIGZyb20gdHJhZmZpYy1yZWxhdGVkIGFpciBwb2xsdXRpb24gd2FzIGFzc29jaWF0ZWQgd2l0aCBpbmNyZWFzZWQgbWVhc3VyZXMgb2YgaW5mbGFtbWF0b3J5IG1hcmtlcnMgaW4geW91dGhzIHdpdGggVHlwZSAxIGRpYWJldGVzCjMpIFtMdW5nIGZ1bmN0aW9uIGFuZCBlbXBoeXNlbWFdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcHVibWVkLzMxNDA4MTM1KXt0YXJnZXQ9Il9ibGFuayJ9IC0gaGlnaGVyIGNvbmNlbnRyYXRpb25zIG9mIG96b25lIChPfjN+KSwgbml0cm9nZW4gb3hpZGVzIChOT354fiksIGJsYWNrIGNhcmJvbiwgYW5kIGZpbmUgcGFydGljbGUgZXhwb3N1cmUgKipQTX4yLjV+KiogLCBhdCBzdHVkeSBiYXNlbGluZSB3ZXJlIHNpZ25pZmljYW50bHkgYXNzb2NpYXRlZCB3aXRoIGdyZWF0ZXIgaW5jcmVhc2VzIGluIHBlcmNlbnQgZW1waHlzZW1hIHBlciAxMCB5ZWFycyAKNCkgW0xvdyBiaXJ0aHdlaWdodF0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wdWJtZWQvMzEzODY2NDMpe3RhcmdldD0iX2JsYW5rIn0gLSBmaW5lIHBhcnRpY2xlIGV4cG9zdXJlKCoqUE1+Mi41fioqKSB3YXMgYXNzb2NpYXRlZCB3aXRoIGxvd2VyIGJpcnRoIHdlaWdodCBpbiBmdWxsLXRlcm0gbGl2ZSBiaXJ0aHMKNSkgW1ZpcmFsIEluZmVjdGlvbl0oaHR0cHM6Ly93d3cudGFuZGZvbmxpbmUuY29tL2RvaS9mdWxsLzEwLjEwODAvMDg5NTgzNzA3MDE2NjU0MzQpe3RhcmdldD0iX2JsYW5rIn0gLSBoaWdoZXIgcmF0ZXMgb2YgaW5mZWN0aW9uIGFuZCBpbmNyZWFzZWQgc2V2ZXJpdHkgb2YgaW5mZWN0aW9uIGFyZSBhc3NvY2lhdGVkIHdpdGggaGlnaGVyIGV4cG9zdXJlcyB0byBwb2xsdXRpb24gbGV2ZWxzIGluY2x1ZGluZyBmaW5lIHBhcnRpY2xlIGV4cG9zdXJlICgqKlBNfjIuNX4qKikKClNlZSB0aGlzIFtyZXZpZXcgYXJ0aWNsZV0oaHR0cHM6Ly93d3cuZnJvbnRpZXJzaW4ub3JnL2FydGljbGVzLzEwLjMzODkvZnB1YmguMjAyMC4wMDAxNC9mdWxsKXt0YXJnZXQ9Il9ibGFuayJ9IGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHNvdXJjZXMgb2YgYWlyIHBvbGx1dGlvbiBhbmQgdGhlIGluZmx1ZW5jZSBvZiBhaXIgcG9sbHV0aW9uIG9uIGhlYWx0aC4KCiMjIyBTcGFyc2UgbW9uaXRvcmluZyBpcyBwcm9ibGVtYXRpYyBmb3IgUHVibGljIEhlYWx0aAoKSGlzdG9yaWNhbGx5LCBlcGlkZW1pb2xvZ2ljYWwgc3R1ZGllcyB3b3VsZCBhc3Nlc3MgdGhlIGluZmx1ZW5jZSBvZiBhaXIgcG9sbHV0aW9uIG9uIGhlYWx0aCBvdXRjb21lcyBieSByZWx5aW5nIG9uIGEgbnVtYmVyIG9mIG1vbml0b3JzIGxvY2F0ZWQgYXJvdW5kIHRoZSBjb3VudHJ5LiAKSG93ZXZlciwgYXMgY2FuIGJlIHNlZW4gaW4gdGhlIGZvbGxvd2luZyBmaWd1cmUsIHRoZXNlIG1vbml0b3JzIGFyZSByZWxhdGl2ZWx5IHNwYXJzZSBpbiBjZXJ0YWluIHJlZ2lvbnMgb2YgdGhlIGNvdW50cnkuIApGdXJ0aGVybW9yZSwgZHJhbWF0aWMgZGlmZmVyZW5jZXMgaW4gcG9sbHV0aW9uIHJhdGVzIGNhbiBiZSBzZWVuIGV2ZW4gd2l0aGluIHRoZSBzYW1lIGNpdHkuCgo8cCBhbGlnbj0iY2VudGVyIj4KICA8aW1nIHdpZHRoPSI0MDAiIHNyYz0iaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNDEzNzI3Mi9iaW4vMTQ3Ni0wNjlYLTEzLTYzLTEuanBnIj4KPC9wPgoKIyMjIyMgW1tzb3VyY2VdXShodHRwczovL2Voam91cm5hbC5iaW9tZWRjZW50cmFsLmNvbS9hcnRpY2xlcy8xMC4xMTg2LzE0NzYtMDY5WC0xMy02Myl7dGFyZ2V0PSJfYmxhbmsifQoKVGhpcyBsYWNrIG9mIGdyYW51bGFyaXR5IGluIGFpciBwb2xsdXRpb24gbW9uaXRvcmluZyBoYXMgaGluZGVyZWQgb3VyIGFiaWxpdHkgdG8gZGlzY2VybiB0aGUgZnVsbCBpbXBhY3Qgb2YgYWlyIHBvbGx1dGlvbiBvbiBoZWFsdGggYW5kIHRvIGlkZW50aWZ5IGF0LXJpc2sgbG9jYXRpb25zLiAKCgojIyMgTWFjaGluZSBsZWFybmluZyBvZmZlcnMgYSBzb2x1dGlvbgoKQW4gW2FydGljbGVdKGh0dHBzOi8vZWhqb3VybmFsLmJpb21lZGNlbnRyYWwuY29tL2FydGljbGVzLzEwLjExODYvMTQ3Ni0wNjlYLTEzLTYzKXt0YXJnZXQ9Il9ibGFuayJ9IHB1Ymxpc2hlZCBpbiB0aGUgKkVudmlyb25tZW50YWwgSGVhbHRoKiBqb3VybmFsIGRlYWx0IHdpdGggdGhpcyBpc3N1ZSBieSB1c2luZyBkYXRhLCBpbmNsdWRpbmcgcG9wdWxhdGlvbiBkZW5zaXR5LCByb2FkIGRlbnNpdHksIGFtb25nIG90aGVyIGZlYXR1cmVzLCB0byBtb2RlbCBvciBwcmVkaWN0IGFpciBwb2xsdXRpb24gbGV2ZWxzIGF0IGEgbW9yZSBsb2NhbGl6ZWQgc2NhbGUgdXNpbmcgbWFjaGluZSBsZWFybmluZyAoTUwpIG1ldGhvZHMuIAoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoPSAiODAwIHB4In0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwgInRoZXBhcGVyLnBuZyIpKQpgYGAKCiMjIyMgey5yZWZlcmVuY2VfYmxvY2t9Cllhbm9za3ksIEouIEQuIGV0IGFsLiBTcGF0aW8tdGVtcG9yYWwgbW9kZWxpbmcgb2YgcGFydGljdWxhdGUgYWlyIHBvbGx1dGlvbiBpbiB0aGUgY29udGVybWlub3VzIFVuaXRlZCBTdGF0ZXMgdXNpbmcgZ2VvZ3JhcGhpYyBhbmQgbWV0ZW9yb2xvZ2ljYWwgcHJlZGljdG9ycy4gKkVudmlyb24gSGVhbHRoKiAxMywgNjMgKDIwMTQpLgoKIyMjIwoKVGhlIGF1dGhvcnMgb2YgdGhpcyBhcnRpY2xlIHN0YXRlIHRoYXQ6Cgo+ICJFeHBvc3VyZSB0byBhdG1vc3BoZXJpYyBwYXJ0aWN1bGF0ZSBtYXR0ZXIgKFBNKSByZW1haW5zIGFuIGltcG9ydGFudCBwdWJsaWMgaGVhbHRoIGNvbmNlcm4sIGFsdGhvdWdoIGl0IHJlbWFpbnMgZGlmZmljdWx0IHRvIHF1YW50aWZ5IGFjY3VyYXRlbHkgYWNyb3NzIGxhcmdlIGdlb2dyYXBoaWMgYXJlYXMgd2l0aCBzdWZmaWNpZW50bHkgaGlnaCBzcGF0aWFsIHJlc29sdXRpb24uIFJlY2VudCBlcGlkZW1pb2xvZ2ljIGFuYWx5c2VzIGhhdmUgZGVtb25zdHJhdGVkIHRoZSBpbXBvcnRhbmNlIG9mIHNwYXRpYWxseS0gYW5kIHRlbXBvcmFsbHktcmVzb2x2ZWQgZXhwb3N1cmUgZXN0aW1hdGVzLCB3aGljaCBzaG93IGxhcmdlciBQTS1tZWRpYXRlZCBoZWFsdGggZWZmZWN0cyBhcyBjb21wYXJlZCB0byBuZWFyZXN0IG1vbml0b3Igb3IgY291bnR5LXNwZWNpZmljIGFtYmllbnQgY29uY2VudHJhdGlvbnMuIiAKCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGg9ICI3MDAgcHgiLCBldmFsID0gRkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsICJkZWF0aHMucG5nIikpCmBgYAoKVGhlIGFydGljbGUgYWJvdmUgZGVtb25zdHJhdGVzIHRoYXQgbWFjaGluZSBsZWFybmluZyBtZXRob2RzIGNhbiBiZSB1c2VkIHRvIHByZWRpY3QgYWlyIHBvbGx1dGlvbiBsZXZlbHMgd2hlbiB0cmFkaXRpb25hbCBtb25pdG9yaW5nIHN5c3RlbXMgYXJlIG5vdCBhdmFpbGFibGUgaW4gYSBwYXJ0aWN1bGFyIGFyZWEgb3Igd2hlbiB0aGVyZSBpcyBub3QgZW5vdWdoIHNwYXRpYWwgZ3JhbnVsYXJpdHkgd2l0aCBjdXJyZW50IG1vbml0b3Jpbmcgc3lzdGVtcy4gCldlIHdpbGwgdXNlIHNpbWlsYXIgbWV0aG9kcyB0byBwcmVkaWN0IGFubnVhbCBhaXIgcG9sbHV0aW9uIGxldmVscyBzcGF0aWFsbHkgd2l0aGluIHRoZSBVUy4KCgojIyBNYWluIFF1ZXN0aW9uCioqKgoKIyMjIyB7Lm1haW5fcXVlc3Rpb25fYmxvY2t9CjxiPjx1PiBPdXIgbWFpbiBxdWVzdGlvbjogPC91PjwvYj4KCjEpIENhbiB3ZSBwcmVkaWN0IGFubnVhbCBhdmVyYWdlIGFpciBwb2xsdXRpb24gY29uY2VudHJhdGlvbnMgYXQgdGhlIGdyYW51bGFyaXR5IG9mIHppcCBjb2RlIHJlZ2lvbmFsIGxldmVscyB1c2luZyBwcmVkaWN0b3JzIHN1Y2ggYXMgZGF0YSBhYm91dCBwb3B1bGF0aW9uIGRlbnNpdHksIHVyYmFuaXphdGlvbiwgcm9hZCBkZW5zaXR5LCBhcyB3ZWxsIGFzLCBzYXRlbGxpdGUgcG9sbHV0aW9uIGRhdGEgYW5kIGNoZW1pY2FsIG1vZGVsaW5nIGRhdGE/CgojIyMjCgojIyBMZWFybmluZyBPYmplY3RpdmVzCioqKgoKSW4gdGhpcyBjYXNlIHN0dWR5LCB3ZSB3aWxsIHdhbGsgeW91IHRocm91Z2ggaW1wb3J0aW5nIGRhdGEgZnJvbSBDU1YgZmlsZXMgYW5kIHBlcmZvcm1pbmcgbWFjaGluZSBsZWFybmluZyBtZXRob2RzIHRvIHByZWRpY3Qgb3VyIG91dGNvbWUgdmFyaWFibGUgb2YgaW50ZXJlc3QgKGluIHRoaXMgY2FzZSBhbm51YWwgZmluZSBwYXJ0aWNsZSBhaXIgcG9sbHV0aW9uIGVzdGltYXRlcykuIAoKV2Ugd2lsbCBlc3BlY2lhbGx5IGZvY3VzIG9uIHVzaW5nIHBhY2thZ2VzIGFuZCBmdW5jdGlvbnMgZnJvbSB0aGUgW2B0aWR5dmVyc2VgXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSwgYW5kIG1vcmUgc3BlY2lmaWNhbGx5IHRoZSBbYHRpZHltb2RlbHNgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdGlkeW1vZGVscy90aWR5bW9kZWxzLnBkZil7dGFyZ2V0PSJfYmxhbmsifSBwYWNrYWdlL2Vjb3N5c3RlbSBwcmltYXJpbHkgZGV2ZWxvcGVkIGFuZCBtYWludGFpbmVkIGJ5IFtNYXggS3Vobl0oaHR0cHM6Ly9yZXNvdXJjZXMucnN0dWRpby5jb20vYXV0aG9ycy9tYXgta3Vobil7dGFyZ2V0PSJfYmxhbmsifSBhbmQgW0RhdmlzIFZhdWdoYW5dKGh0dHBzOi8vcmVzb3VyY2VzLnJzdHVkaW8uY29tL2F1dGhvcnMvZGF2aXMtdmF1Z2hhbil7dGFyZ2V0PSJfYmxhbmsifS4gClRoaXMgcGFja2FnZSBsb2FkcyBtb3JlIG1vZGVsaW5nIHJlbGF0ZWQgcGFja2FnZXMgbGlrZSBgcnNhbXBsZWAsIGByZWNpcGVzYCwgYHBhcnNuaXBgLCBgeWFyZHN0aWNrYCwgYHdvcmtmbG93c2AsIGFuZCBgdHVuZWAgcGFja2FnZXMuIAoKVGhlIHRpZHl2ZXJzZSBpcyBhIGxpYnJhcnkgb2YgcGFja2FnZXMgY3JlYXRlZCBieSBSU3R1ZGlvLiAKV2hpbGUgc29tZSBzdHVkZW50cyBtYXkgYmUgZmFtaWxpYXIgd2l0aCBwcmV2aW91cyBSIHByb2dyYW1taW5nIHBhY2thZ2VzLCB0aGVzZSBwYWNrYWdlcyBtYWtlIGRhdGEgc2NpZW5jZSBpbiBSIGVzcGVjaWFsbHkgZWZmaWNpZW50LgoKCmBgYHtyLCBvdXQud2lkdGggPSAiMjAlIiwgZWNobyA9IEZBTFNFLCBmaWcuYWxpZ24gPSJjZW50ZXIifQppbmNsdWRlX2dyYXBoaWNzKCJodHRwczovL3RpZHl2ZXJzZS50aWR5dmVyc2Uub3JnL2xvZ28ucG5nIikKYGBgCgpUaGUgc2tpbGxzLCBtZXRob2RzLCBhbmQgY29uY2VwdHMgdGhhdCBzdHVkZW50cyB3aWxsIGJlIGZhbWlsaWFyIHdpdGggYnkgdGhlIGVuZCBvZiB0aGlzIGNhc2Ugc3R1ZHkgYXJlOgoKCkRhdGEgc2NpZW5jZSBza2lsbHM6ICAKICAKMS4gRmFtaWxpYXJpdHkgd2l0aCB0aGUgdGlkeW1vZGVscyBlY29zeXN0ZW0KMi4gQWJpbGl0eSB0byBldmFsdWF0ZSBjb3JyZWxhdGlvbiBhbW9uZyBwcmVkaWN0b3IgdmFyaWFibGVzIChgY29ycnBsb3RgIGFuZCBgR0dhbGx5YCkKMy4gQWJpbGl0eSB0byBpbXBsZW1lbnQgdGlkeW1vZGVscyBwYWNrYWdlcyBzdWNoIGFzIGByc2FtcGxlYCB0byBzcGxpdCB0aGUgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldHMgYXMgd2VsbCBhcyBjcm9zcyB2YWxpZGF0aW9uIHNldHMuCjQuIEFiaWxpdHkgdG8gdXNlIHRoZSBgcmVjaXBlc2AsIGBwYXJzbmlwYCwgYW5kIGB3b3JrZmxvd3NgIHRvIHRyYWluIGFuZCB0ZXN0IGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgYW5kIHJhbmRvbSBmb3Jlc3QgbW9kZWwKNS4gRGVtb25zdHJhdGUgaG93IHRvIHZpc3VhbGl6ZSBnZW8tc3BhdGlhbCBkYXRhIHVzaW5nIGBnZ3Bsb3QyYAoKU3RhdGlzdGljYWwgY29uY2VwdHMgYW5kIG1ldGhvZHM6ICAKICAKMS4gQmFzaWMgdW5kZXJzdGFuZGluZyB0aGUgdXRpbGl0eSBvZiBtYWNoaW5lIGxlYXJuaW5nIGZvciBwcmVkaWN0aW9uIGFuZCBjbGFzc2lmaWNhdGlvbgoyLiBVbmRlcnN0YW5kaW5nIG9mIHRoZSBuZWVkIGZvciB0cmFpbmluZyBhbmQgdGVzdCBzZXQKMy4gVW5kZXJzdGFuZGluZyBvZiB0aGUgdXRpbGl0eSBvZiBjcm9zcyB2YWxpZGF0aW9uCjQuIFVuZGVyc3RhbmRpbmcgb2YgcmFuZG9tIGZvcmVzdAo1LiBIb3cgdG8gaW50ZXJwcmV0IHJvb3QgbWVhbiBzcXVhcmVkIGVycm9yIChybXNlKSB0byBhc3Nlc3MgcGVyZm9ybWFuY2UgZm9yIHByZWRpY3Rpb24KCgoKYGBge3IsIG91dC53aWR0aCA9ICIxMDBweCIsIGVjaG8gPSBGQUxTRSwgZmlnLmFsaWduID0iY2VudGVyIn0KaW5jbHVkZV9ncmFwaGljcygiaHR0cHM6Ly9wYnMudHdpbWcuY29tL21lZGlhL0RrQkZwU3NXNEFJeXlJTi5wbmciKQpgYGAKCgpXZSB3aWxsIGJlZ2luIGJ5IGxvYWRpbmcgdGhlIHBhY2thZ2VzIHRoYXQgd2Ugd2lsbCBuZWVkOgoKYGBge3J9CmxpYnJhcnkoaGVyZSkKbGlicmFyeShyZWFkcikKbGlicmFyeShkcGx5cikKbGlicmFyeShza2ltcikKbGlicmFyeShzdW1tYXJ5dG9vbHMpCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkoY29ycnBsb3QpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KEdHYWxseSkKbGlicmFyeSh0aWR5bW9kZWxzKQpsaWJyYXJ5KHdvcmtmbG93cykKbGlicmFyeSh2aXApCmxpYnJhcnkodHVuZSkKbGlicmFyeShyYW5kb21Gb3Jlc3QpCmxpYnJhcnkoZG9QYXJhbGxlbCkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkobHdnZW9tKQpsaWJyYXJ5KHNmKQpsaWJyYXJ5KG1hcHMpCmxpYnJhcnkocm5hdHVyYWxlYXJ0aCkKbGlicmFyeShyZ2VvcykKbGlicmFyeShwYXRjaHdvcmspCmBgYAoKCiBQYWNrYWdlICAgfCBVc2UgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCi0tLS0tLS0tLS0gfC0tLS0tLS0tLS0tLS0KW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9qZW5ueWJjL2hlcmVfaGVyZSl7dGFyZ2V0PSJfYmxhbmsifSAgICAgICB8IHRvIGVhc2lseSBsb2FkIGFuZCBzYXZlIGRhdGEKW3JlYWRyXShodHRwczovL3JlYWRyLnRpZHl2ZXJzZS5vcmcvKXt0YXJnZXQ9Il9ibGFuayJ9ICAgICAgfCB0byBpbXBvcnQgQ1NWIGZpbGVzCltkcGx5cl0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSAgICAgIHwgdG8gdmlldy9hcnJhbmdlL2ZpbHRlci9zZWxlY3QvY29tcGFyZSBzcGVjaWZpYyBzdWJzZXRzIG9mIGRhdGEgCltza2ltcl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3NraW1yL2luZGV4Lmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gICAgICB8IHRvIGdldCBhbiBvdmVydmlldyBvZiBkYXRhCltzdW1tYXJ5dG9vbHNdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9za2ltci9pbmRleC5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9ICAgICAgfCB0byBnZXQgYW4gb3ZlcnZpZXcgb2YgZGF0YSBpbiBhIGRpZmZlcmVudCBzdHlsZQpbbWFncml0dHJdKGh0dHBzOi8vbWFncml0dHIudGlkeXZlcnNlLm9yZy9hcnRpY2xlcy9tYWdyaXR0ci5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9ICAgfCB0byB1c2UgdGhlIGAlPD4lYCBwaXBwaW5nIG9wZXJhdG9yIApbY29ycnBsb3RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9jb3JycGxvdC92aWduZXR0ZXMvY29ycnBsb3QtaW50cm8uaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSB8IHRvIG1ha2UgbGFyZ2UgY29ycmVsYXRpb24gcGxvdHMKW0dHYWxseV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL0dHYWxseS9HR2FsbHkucGRmKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gbWFrZSBzbWFsbGVyIGNvcnJlbGF0aW9uIHBsb3RzICAKW3RpZHltb2RlbHNdKGh0dHBzOi8vd3d3LnRpZHltb2RlbHMub3JnKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gbG9hZCBpbiBhIHNldCBvZiBwYWNrYWdlcyAoYnJvb20sIGRpYWxzLCBpbmZlciwgcGFyc25pcCwgcHVycnIsIHJlY2lwZXMsIHJzYW1wbGUsIHRpYmJsZSwgeWFyZHN0aWNrKQpbcnNhbXBsZV0oaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby9yc2FtcGxlL2FydGljbGVzL0Jhc2ljcy5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9ICAgfCB0byBzcGxpdCB0aGUgZGF0YSBpbnRvIHRlc3RpbmcgYW5kIHRyYWluaW5nIHNldHM7IHRvIHNwbGl0IHRoZSB0cmFpbmluZyBzZXQgZm9yIGNyb3NzLXZhbGlkYXRpb24gIApbcmVjaXBlc10oaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby9yZWNpcGVzLyl7dGFyZ2V0PSJfYmxhbmsifSAgIHwgdG8gcHJlLXByb2Nlc3MgZGF0YSBmb3IgbW9kZWxpbmcgaW4gYSB0aWR5IGFuZCByZXByb2R1Y2libGUgd2F5IGFuZCB0byBleHRyYWN0IHByZS1wcm9jZXNzZWQgZGF0YSAobWFqb3IgZnVuY3Rpb25zIGFyZSBgcmVjaXBlKClgICwgYHByZXAoKWAgYW5kIHZhcmlvdXMgdHJhbnNmb3JtYXRpb24gYHN0ZXBfKigpYCBmdW5jdGlvbnMsIGFzIHdlbGwgYXMgYGp1aWNlKClgIC0gZXh0cmFjdHMgZmluYWwgcHJlLXByb2Nlc3NlZCB0cmFpbmluZyBkYXRhIGFuZCBgYmFrZSgpYCAtIGFwcGxpZXMgcmVjaXBlIHN0ZXBzIHRvIHRlc3RpbmcgZGF0YSkuIFNlZSBbaGVyZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3JlY2lwZXMvcmVjaXBlcy5wZGYpe3RhcmdldD0iX2JsYW5rIn0gIGZvciBtb3JlIGluZm8uCltwYXJzbmlwXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3BhcnNuaXAvKXt0YXJnZXQ9Il9ibGFuayJ9ICAgfCBhbiBpbnRlcmZhY2UgdG8gY3JlYXRlIG1vZGVscyAobWFqb3IgZnVuY3Rpb25zIGFyZSBgZml0KClgLCBgc2V0X2VuZ2luZSgpYCkKW3lhcmRzdGlja10oaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby95YXJkc3RpY2svKXt0YXJnZXQ9Il9ibGFuayJ9ICAgfCB0byBldmFsdWF0ZSB0aGUgcGVyZm9ybWFuY2Ugb2YgbW9kZWxzClticm9vbV0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy9ibG9nLzIwMTgvMDcvYnJvb20tMC01LTAvKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gZ2V0IHRpZHkgb3V0cHV0IGZvciBvdXIgbW9kZWwgZml0IGFuZCBwZXJmb3JtYW5jZQpbZ2dwbG90Ml0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvKXt0YXJnZXQ9Il9ibGFuayJ9ICAgIHwgdG8gbWFrZSB2aXN1YWxpemF0aW9ucyB3aXRoIG11bHRpcGxlIGxheWVycwpbZGlhbHNdKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvYmxvZy8yMDE5LzEwL2RpYWxzLTAtMC0zLyl7dGFyZ2V0PSJfYmxhbmsifSB8IHRvIHNwZWNpZnkgaHlwZXItcGFyYW1ldGVyIHR1bmluZwpbdHVuZV0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSB8IHRvIHBlcmZvcm0gY3Jvc3MgdmFsaWRhdGlvbiwgdHVuZSBoeXBlci1wYXJhbWV0ZXJzLCBhbmQgZ2V0IHBlcmZvcm1hbmNlIG1ldHJpY3MKW3dvcmtmbG93c10oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3dvcmtmbG93cy92ZXJzaW9ucy8wLjEuMSl7dGFyZ2V0PSJfYmxhbmsifXwgdG8gY3JlYXRlIG1vZGVsaW5nIHdvcmtmbG93IHRvIHN0cmVhbWxpbmUgdGhlIG1vZGVsaW5nIHByb2Nlc3MKW3ZpcF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3ZpcC92aXAucGRmKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gY3JlYXRlIHZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdHMKW3JhbmRvbUZvcmVzdF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3JhbmRvbUZvcmVzdC9yYW5kb21Gb3Jlc3QucGRmKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gcGVyZm9ybSB0aGUgcmFuZG9tIGZvcmVzdCBhbmFseXNpcwpbZG9QYXJhbGxlbF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2RvUGFyYWxsZWwvZG9QYXJhbGxlbC5wZGYpIHwgdG8gZml0IGNyb3NzIHZhbGlkYXRpb24gc2FtcGxlcyBpbiBwYXJhbGxlbCAKW3N0cmluZ3JdKGh0dHBzOi8vc3RyaW5nci50aWR5dmVyc2Uub3JnL2FydGljbGVzL3N0cmluZ3IuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSAgICB8IHRvIG1hbmlwdWxhdGUgdGhlIHRleHQgdGhlIG1hcCBkYXRhClt0aWR5cl0oaHR0cHM6Ly90aWR5ci50aWR5dmVyc2Uub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSAgICAgIHwgdG8gc2VwYXJhdGUgZGF0YSB3aXRoaW4gYSBjb2x1bW4gaW50byBtdWx0aXBsZSBjb2x1bW5zCltybmF0dXJhbGVhcnRoXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcm5hdHVyYWxlYXJ0aC9SRUFETUUuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSB8IHRvIGdldCB0aGUgZ2VvbWV0cnkgZGF0YSBmb3IgdGhlIGVhcnRoIHRvIHBsb3QgdGhlIFVTClttYXBzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvbWFwcy9tYXBzLnBkZil7dGFyZ2V0PSJfYmxhbmsifSB8IHRvIGdldCBtYXAgZGF0YWJhc2UgZGF0YSBhYm91dCBjb3VudGllcyB0byBkcmF3IHRoZW0gb24gb3VyIFVTIG1hcApbc2ZdKGh0dHBzOi8vci1zcGF0aWFsLmdpdGh1Yi5pby9zZi8pe3RhcmdldD0iX2JsYW5rIn0gfCB0byBjb252ZXJ0IHRoZSBtYXAgZGF0YSBpbnRvIGEgZGF0YSBmcmFtZQpbbHdnZW9tXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvbHdnZW9tL2x3Z2VvbS5wZGYpe3RhcmdldD0iX2JsYW5rIn0gfCB0byB1c2UgdGhlIGBzZmAgZnVuY3Rpb24gdG8gY29udmVydCBtYXAgZ2VvZ3JhcGhpY2FsIGRhdGEKW3JnZW9zXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcmdlb3Mvcmdlb3MucGRmKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gdXNlIGdlb21ldHJ5IGRhdGEKW3BhdGNod29ya10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3BhdGNod29yay9wYXRjaHdvcmsucGRmKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gYWxsb3cgcGxvdHMgdG8gYmUgY29tYmluZWQKX19fCgoKVGhlIGZpcnN0IHRpbWUgd2UgdXNlIGEgZnVuY3Rpb24sIHdlIHdpbGwgdXNlIHRoZSBgOjpgIHRvIGluZGljYXRlIHdoaWNoIHBhY2thZ2Ugd2UgYXJlIHVzaW5nLiAKVW5sZXNzIHdlIGhhdmUgb3ZlcmxhcHBpbmcgZnVuY3Rpb24gbmFtZXMsIHRoaXMgaXMgbm90IG5lY2Vzc2FyeSwgYnV0IHdlIHdpbGwgaW5jbHVkZSBpdCBoZXJlIHRvIGJlIGluZm9ybWF0aXZlIGFib3V0IHdoZXJlIHRoZSBmdW5jdGlvbnMgd2Ugd2lsbCB1c2UgY29tZSBmcm9tLgoKCiMjIENvbnRleHQKKioqCgpUaGUgW1N0YXRlIG9mIEdsb2JhbCBBaXJdKGh0dHBzOi8vd3d3LnN0YXRlb2ZnbG9iYWxhaXIub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSBpcyBhIHJlcG9ydCByZWxlYXNlZCBldmVyeSB5ZWFyIHRvIGNvbW11bmljYXRlIHRoZSBpbXBhY3Qgb2YgYWlyIHBvbGx1dGlvbiBvbiBwdWJsaWMgaGVhbHRoLiAKClRoZSBbU3RhdGUgb2YgR2xvYmFsIEFpciAyMDE5IHJlcG9ydF0oaHR0cHM6Ly93d3cuc3RhdGVvZmdsb2JhbGFpci5vcmcvc2l0ZXMvZGVmYXVsdC9maWxlcy9zb2dhXzIwMTlfcmVwb3J0LnBkZil7dGFyZ2V0PSJfYmxhbmsifQp3aGljaCB1c2VzIGRhdGEgZnJvbSAyMDE3IHN0YXRlZCB0aGF0OgoKPiBBaXIgcG9sbHV0aW9uIGlzIHRoZSAqKmZpZnRoKiogbGVhZGluZyByaXNrIGZhY3RvciBmb3IgbW9ydGFsaXR5IHdvcmxkd2lkZS4gSXQgaXMgcmVzcG9uc2libGUgZm9yIG1vcmUKZGVhdGhzIHRoYW4gbWFueSBiZXR0ZXIta25vd24gcmlzayBmYWN0b3JzIHN1Y2ggYXMgbWFsbnV0cml0aW9uLCBhbGNvaG9sIHVzZSwgYW5kIHBoeXNpY2FsIGluYWN0aXZpdHkuCkVhY2ggeWVhciwgKiptb3JlKiogcGVvcGxlIGRpZSBmcm9tIGFpciBwb2xsdXRpb27igJNyZWxhdGVkIGRpc2Vhc2UgdGhhbiBmcm9tIHJvYWQgKip0cmFmZmljIGluanVyaWVzKiogb3IgKiptYWxhcmlhKiouCgo8cCBhbGlnbj0iY2VudGVyIj4KPGltZyB3aWR0aD0iNjAwIiBzcmM9Imh0dHBzOi8vd3d3LmhlYWx0aGVmZmVjdHMub3JnL3NpdGVzL2RlZmF1bHQvZmlsZXMvU29HQS1GaWd1cmVzLTAxLmpwZyI+CjwvcD4KCiMjIyMjIFtbc291cmNlXV0oaHR0cHM6Ly93d3cuc3RhdGVvZmdsb2JhbGFpci5vcmcvc2l0ZXMvZGVmYXVsdC9maWxlcy9zb2dhXzIwMTlfcmVwb3J0LnBkZil7dGFyZ2V0PSJfYmxhbmsifQoKVGhlIHJlcG9ydCBhbHNvIHN0YXRlZCB0aGF0OgoKPiBJbiAyMDE3LCBhaXIgcG9sbHV0aW9uIGlzIGVzdGltYXRlZCB0byBoYXZlIGNvbnRyaWJ1dGVkIHRvIGNsb3NlIHRvIDUgbWlsbGlvbgpkZWF0aHMgZ2xvYmFsbHkg4oCUIG5lYXJseSAqKjEgaW4gZXZlcnkgMTAgZGVhdGhzKiouCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGg9IjgwMHB4In0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwiMjAxN2RlYXRocy5wbmciKSkKYGBgCgojIyMjIyBbW3NvdXJjZV1dKGh0dHBzOi8vd3d3LnN0YXRlb2ZnbG9iYWxhaXIub3JnL3NpdGVzL2RlZmF1bHQvZmlsZXMvc29nYV8yMDE5X2ZhY3Rfc2hlZXQucGRmKXt0YXJnZXQ9Il9ibGFuayJ9CgpUaGUgW1N0YXRlIG9mIEdsb2JhbCBBaXIgMjAxOCByZXBvcnRdKGh0dHBzOi8vd3d3LnN0YXRlb2ZnbG9iYWxhaXIub3JnL3NpdGVzL2RlZmF1bHQvZmlsZXMvc29nYS0yMDE4LXJlcG9ydC5wZGYpe3RhcmdldD0iX2JsYW5rIn0gdXNpbmcgZGF0YSBmcm9tIDIwMTYgd2hpY2ggc2VwYXJhdGVkIGRpZmZlcmVudCB0eXBlcyBvZiBhaXIgcG9sbHV0aW9uLCBmb3VuZCB0aGF0ICoqcGFydGljdWxhdGUgcG9sbHV0aW9uIHdhcyBwYXJ0aWN1bGFybHkgYXNzb2NpYXRlZCB3aXRoIG1vcnRhbGl0eSoqLgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoPSI4MDBweCJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsIjIwMTdtb3J0YWxpdHkucG5nIikpCmBgYAoKVGhlIDIwMTkgcmVwb3J0IHNob3dzIHRoYXQgdGhlIGhpZ2hlc3QgbGV2ZWxzIG9mIGZpbmUgcGFydGljdWxhdGUgcG9sbHV0aW9uIG9jY3VycyBpbiBBZnJpY2EgYW5kIEFzaWEgYW5kIHRoYXQ6Cgo+IE1vcmUgdGhhbiAqKjkwJSoqIG9mIHBlb3BsZSB3b3JsZHdpZGUgbGl2ZSBpbiBhcmVhcyAqKmV4Y2VlZGluZyoqIHRoZSBXb3JsZCBIZWFsdGggT3JnYW5pemF0aW9uIChXSE8pICoqR3VpZGVsaW5lKiogZm9yIGhlYWx0aHkgYWlyLiBNb3JlIHRoYW4gaGFsZiBsaXZlIGluIGFyZWFzIHRoYXQgZG8gbm90IGV2ZW4gbWVldCBXSE8ncyBsZWFzdC1zdHJpbmdlbnQgYWlyIHF1YWxpdHkgdGFyZ2V0LgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoPSI4MDBweCJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsIlBNd29ybGQucG5nIikpCmBgYAoKIyMjIyMgW1tzb3VyY2VdXShodHRwczovL3d3dy5zdGF0ZW9mZ2xvYmFsYWlyLm9yZy9zaXRlcy9kZWZhdWx0L2ZpbGVzL3NvZ2FfMjAxOV9mYWN0X3NoZWV0LnBkZil7dGFyZ2V0PSJfYmxhbmsifQoKTG9va2luZyBhdCB0aGUgVVMgc3BlY2lmaWNhbGx5LCBhaXIgcG9sbHV0aW9uIGxldmVscyBhcmUgZ2VuZXJhbGx5IGltcHJvdmluZywgd2l0aCBkZWNsaW5pbmcgbmF0aW9uYWwgYWlyIHBvbGx1dGFudCBjb25jZW50cmF0aW9uIGF2ZXJhZ2VzIGFzIHNob3duIGZyb20gdGhlIDIwMTkgWypPdXIgTmF0aW9uJ3MgQWlyKl0oaHR0cHM6Ly9naXNwdWIuZXBhLmdvdi9haXIvdHJlbmRzcmVwb3J0LzIwMTkvI2hvbWUpe3RhcmdldD0iX2JsYW5rIn0gcmVwb3J0IGZyb20gdGhlIFVTIEVudmlyb25tZW50YWwgUHJvdGVjdGlvbiBBZ2VuY3kgKEVQQSk6IAoKYGBge3IsIGVjaG8gPSBGQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwgIlVTLnBuZyIpKQpgYGAKCiMjIyMjIFtbc291cmNlXV0oaHR0cHM6Ly9naXNwdWIuZXBhLmdvdi9haXIvdHJlbmRzcmVwb3J0LzIwMTkvZG9jdW1lbnRhdGlvbi9BaXJUcmVuZHNfRmx5ZXIucGRmKXt0YXJnZXQ9Il9ibGFuayJ9CgpIb3dldmVyLCBhaXIgcG9sbHV0aW9uICoqY29udGludWVzIHRvIGNvbnRyaWJ1dGUgdG8gaGVhbHRoIHJpc2sgZm9yIEFtZXJpY2FucyoqLCBpbiBwYXJ0aWN1bGFyIGluICoqcmVnaW9ucyB3aXRoIGhpZ2hlciB0aGFuIG5hdGlvbmFsIGF2ZXJhZ2UgcmF0ZXMqKiBvZiBwb2xsdXRpb24gdGhhdCBhY3R1YWxseSBhdCB0aW1lIGV4Y2VlZCB0aGUgV0hPJ3MgcmVjb21tZW5kZWQgbGV2ZWwuIApUaHVzLCBpdCBpcyBpbXBvcnRhbnQgdG8gb2J0YWluIGhpZ2ggc3BhdGlhbCBncmFudWxhcml0eSBpbiBlc3RpbWF0ZXMgb2YgYWlyIHBvbGx1dGlvbiBpbiBvcmRlciB0byBpZGVudGlmeSBsb2NhdGlvbnMgd2hlcmUgcG9wdWxhdGlvbnMgYXJlIGV4cGVyaWVuY2luZyBoYXJtZnVsIGxldmVscyBvZiBleHBvc3VyZS4KCllvdSBjYW4gc2VlIHRoYXQgY3VycmVudCBhaXIgcXVhbGl0eSBjb25kaXRpb25zIGF0IHRoaXMgW3dlYnNpdGVdKGh0dHBzOi8vYXFpY24ub3JnL2NpdHkvdXNhLyl7dGFyZ2V0PSJfYmxhbmsifSBhbmQgeW91IHdpbGwgbm90aWNlIHZhcmlhdGlvbiBhY3Jvc3MgZGlmZmVyZW50IGNpdGllcy4KCkZvciBleGFtcGxlLCBoZXJlIGFyZSB0aGUgY29uZGl0aW9ucyBpbiBUb3Bla2EgS2Fuc2FzIGF0IHRoZSB0aW1lIHRoaXMgY2FzZSBzdHVkeSB3YXMgY3JlYXRlZDoKCmBgYHtyLCBlY2hvID0gRkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsICJLYW5zYXMucG5nIikpCmBgYAoKSXQgcmVwb3J0cyBwYXJ0aWN1bGF0ZSB2YWx1ZXMgdXNpbmcgd2hhdCBpcyBjYWxsZWQgdGhlIFtBaXIgUXVhbGl0eSBJbmRleF0oaHR0cHM6Ly93d3cuYWlybm93Lmdvdi9pbmRleC5jZm0/YWN0aW9uPWFxaWJhc2ljcy5hcWkpe3RhcmdldD0iX2JsYW5rIn0gKEFRSSkuClRoaXMgW2NhbGN1bGF0b3JdKGh0dHBzOi8vYWlybm93Lmdvdi9pbmRleC5jZm0/YWN0aW9uPWFpcm5vdy5jYWxjdWxhdG9yKXt0YXJnZXQ9Il9ibGFuayJ9IGluZGljYXRlcyB0aGF0IDExNCBBUUkgaXMgZXF1aXZhbGVudCB0byA0MC43IHVnL21eM14gYW5kIGlzIGNvbnNpZGVyZWQgdW5oZWFsdGh5IGZvciBzZW5zaXRpdmUgaW5kaXZpZHVhbHMuClRodXMsIHNvbWUgYXJlYXMgZXhjZWVkIHRoZSBXSE8gYW5udWFsIGV4cG9zdXJlIGd1aWRlbGluZSAoMTAgdWcvbV4zXikgYW5kIHRoaXMgbWF5IGFkdmVyc2VseSBhZmZlY3QgdGhlIGhlYWx0aCBvZiBwZW9wbGUgbGl2aW5nIGluIHRoZXNlIGxvY2F0aW9ucy4KCkFkdmVyc2UgaGVhbHRoIGVmZmVjdHMgaGF2ZSBiZWVuIGFzc29jaWF0ZWQgd2l0aCBwb3B1bGF0aW9ucyBleHBlcmllbmNpbmcgaGlnaGVyIHBvbGx1dGlvbiBleHBvc3VyZSBkZXNwaXRlIHRoZSBsZXZlbHMgYmVpbmcgYmVsb3cgc3VnZ2VzdGVkIGd1aWRlbGluZXMuIApBbHNvLCBpdCBhcHBlYXJzIHRoYXQgdGhlIGNvbXBvc2l0aW9uIG9mIHRoZSBwYXJ0aWN1bGF0ZSBtYXRlciBhbmQgdGhlIGluZmx1ZW5jZSBvZiBvdGhlciBkZW1vZ3JhcGhpYyBmYWN0b3JzIG1heSBtYWtlIHNwZWNpZmljIHBvcHVsYXRpb25zIG1vcmUgYXQgcmlzayBmb3IgYWR2ZXJzZSBoZWFsdGggZWZmZWN0cyBkdWUgdG8gYWlyIHBvbGx1dGlvbi4gCkZvciBleGFtcGxlLCBzZWUgdGhpcyBbYXJ0aWNsZV0oaHR0cHM6Ly93d3cubmVqbS5vcmcvZG9pL2Z1bGwvMTAuMTA1Ni9ORUpNb2ExNzAyNzQ3KXt0YXJnZXQ9Il9ibGFuayJ9IGZvciBtb3JlIGRldGFpbHMuCgpUaGUgbW9uaXRvciBkYXRhIHRoYXQgd2Ugd2lsbCB1c2UgaW4gdGhpcyBjYXNlIHN0dWR5IGNvbWUgZnJvbSBhIHN5c3RlbSBvZiBtb25pdG9ycyBpbiB3aGljaCByb3VnaGx5IDkwJSBhcmUgbG9jYXRlZCB3aXRoaW4gY2l0aWVzLiAKSGVuY2UsIHRoZXJlIGlzIGFuICoqZXF1aXR5IGlzc3VlKiogaW4gdGVybXMgb2YgY2FwdHVyaW5nIHRoZSBhaXIgcG9sbHV0aW9uIGxldmVscyBvZiBtb3JlIHJ1cmFsIGFyZWFzLiAKVG8gZ2V0IGEgYmV0dGVyIHNlbnNlIG9mIHRoZSBwb2xsdXRpb24gZXhwb3N1cmVzIGZvciB0aGUgaW5kaXZpZHVhbHMgbGl2aW5nIGluIHRoZXNlIGFyZWFzLCBtZXRob2RzIGxpa2UgbWFjaGluZSBsZWFybmluZyBjYW4gYmUgdXNlZnVsIHRvIGVzdGltYXRlIGFpciBwb2xsdXRpb24gbGV2ZWxzIGluICoqYXJlYXMgd2l0aCBsaXR0bGUgdG8gbm8gbW9uaXRvcmluZyoqLiAKU3BlY2lmaWNhbGx5LCB0aGVzZSBtZXRob2RzIGNhbiBiZSB1c2VkIHRvIGVzdGltYXRlIGFpciBwb2xsdXRpb24gaW4gdGhlc2UgbG93IG1vbml0b3JpbmcgYXJlYXMgc28gdGhhdCB3ZSBjYW4gbWFrZSBhIG1hcCBsaWtlIHRoaXMgd2hlcmUgd2UgaGF2ZSBhbm51YWwgZXN0aW1hdGVzIGZvciBhbGwgb2YgdGhlIGNvbnRpZ3VvdXMgVVM6Cgo8cCBhbGlnbj0iY2VudGVyIj4KICA8aW1nIHdpZHRoPSI2MDAiIHNyYz0iaHR0cHM6Ly9hcmMtYW5nbGVyZmlzaC13YXNocG9zdC1wcm9kLXdhc2hwb3N0LnMzLmFtYXpvbmF3cy5jb20vcHVibGljL1NBV09FR0JYTVZHUTdBUzVQWjZVVU9YNkZZLnBuZyI+CjwvcD4KCiMjIyMjIFtbc291cmNlXV0oaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS91cmw/c2E9aSZ1cmw9aHR0cHMlM0ElMkYlMkZ3d3cud2FzaGluZ3RvbnBvc3QuY29tJTJGYnVzaW5lc3MlMkYyMDE5JTJGMTAlMkYyMyUyRmFpci1wb2xsdXRpb24taXMtZ2V0dGluZy13b3JzZS1kYXRhLXNob3ctbW9yZS1wZW9wbGUtYXJlLWR5aW5nJTJGJnBzaWc9QU92VmF3M3YtWkRUQlBuTFAyTVl0S2YzVW5kaiZ1c3Q9MTU4NTc4NDQ3OTA2ODAwMCZzb3VyY2U9aW1hZ2VzJmNkPXZmZSZ2ZWQ9MENBSVFqUnhxRndvVENQQ3luOWZ4eGVnQ0ZRQUFBQUFkQUFBQUFCQWQpe3RhcmdldD0iX2JsYW5rIn0KClRoaXMgaXMgd2hhdCB3ZSBhaW0gdG8gYWNoaWV2ZSBpbiB0aGlzIGNhc2Ugc3R1ZHkuCgojIyBMaW1pdGF0aW9ucwoqKioKClRoZXJlIGFyZSBzb21lIGltcG9ydGFudCBjb25zaWRlcmF0aW9ucyByZWdhcmRpbmcgdGhlIGRhdGEgYW5hbHlzaXMgaW4gdGhpcyBjYXNlIHN0dWR5IHRvIGtlZXAgaW4gbWluZDogCgoxLiBUaGUgZGF0YSBkbyBub3QgaW5jbHVkZSBpbmZvcm1hdGlvbiBhYm91dCB0aGUgY29tcG9zaXRpb24gb2YgcGFydGljdWxhdGUgbWF0ZXIuIERpZmZlcmVudCB0eXBlcyBvZiBwYXJ0aWN1bGF0ZXMgbWF5IGJlIG1vcmUgYmVuaWduIG9yIGRlbGV0ZXJpb3VzIGZvciBoZWFsdGggb3V0Y29tZXMuCgoyLiBPdXRkb29yIHBvbGx1dGlvbiBsZXZlbHMgYXJlIG5vdCBuZWNlc3NhcmlseSBhbiBpbmRpY2F0aW9uIG9mIGluZGl2aWR1YWwgZXhwb3N1cmVzLiBQZW9wbGUgc3BlbmQgZGlmZmVyaW5nIGFtb3VudHMgb2YgdGltZSBpbmRvb3JzIGFuZCBvdXRkb29ycyBhbmQgYXJlIGV4cG9zZWQgdG8gZGlmZmVyZW50IHBvbGx1dGlvbiBsZXZlbHMgaW5kb29ycy4gUmVzZWFyY2hlcnMgYXJlIG5vdyBkZXZlbG9waW5nIHBlcnNvbmFsIG1vbml0b3Jpbmcgc3lzdGVtcyB0byB0cmFjayBhaXIgcG9sbHV0aW9uIGxldmVscyBvbiB0aGUgcGVyc29uYWwgbGV2ZWwuCgozLiBPdXIgYW5hbHlzaXMgd2lsbCB1c2UgYW5udWFsIG1lYW4gZXN0aW1hdGVzIG9mIHBvbGx1dGlvbiBsZXZlbHMsIGJ1dCB0aGVzZSBjYW4gdmFyeSBncmVhdGx5IGJ5IHNlYXNvbiwgZGF5IGFuZCBldmVuIGhvdXIuIFRoZXJlIGFyZSBkYXRhIHNvdXJjZXMgdGhhdCBoYXZlIGZpbmVyIGxldmVscyBvZiB0ZW1wb3JhbCBkYXRhLCBob3dldmVyIHdlIGFyZSBpbnRlcmVzdGVkIGluIGxvbmcgdGVybSBleHBvc3VyZXMsIGFzIHRoZXNlIGFwcGVhciB0byBiZSB0aGUgbW9zdCBpbmZsdWVudGlhbCBmb3IgaGVhbHRoIG91dGNvbWVzLCBzbyB3ZSBjaG9zZSB0byB1c2UgYW5udWFsIGxldmVsIGRhdGEuIAoKCiMgKipXaGF0IGFyZSB0aGUgZGF0YT8qKiB7I3doYXRhcmV0aGVkYXRhfQoqKioKCldoZW4gdXNpbmcgbWFjaGluZSBsZWFybmluZyBmb3IgcHJlZGljdGlvbiwgdGhlcmUgYXJlIHR3byBtYWluIHR5cGVzIG9mIGRhdGEgb2YgaW50ZXJlc3Q6CgoxLiBBbiAqKmNvbnRpbnVvdXMqKiBvdXRjb21lIHZhcmlhYmxlIHRoYXQgd2Ugd2FudCB0byBwcmVkaWN0IAoyLiBBIHNldCBvZiBmZWF0dXJlKHMpIChvciBwcmVkaWN0b3IgdmFyaWFibGVzKSB0aGF0IHdlIHVzZSB0byBwcmVkaWN0IHRoZSBvdXRjb21lIHZhcmlhYmxlCgpUaGUgKipvdXRjb21lIHZhcmlhYmxlKiogaXMgd2hhdCBhcmUgdHJ5aW5nIHRvICoqcHJlZGljdCoqLiAKVG8gYnVpbGQgKG9yIHRyYWluKSBvdXIgbW9kZWwsIHdlIHVzZSBib3RoIHRoZSBvdXRjb21lIGFuZCBmZWF0dXJlcy4KVGhlIGdvYWwgaXMgdG8gaWRlbnRpZnkgaW5mb3JtYXRpdmUgZmVhdHVyZXMgdGhhdCBjYW4gZXhwbGFpbiBhIGxhcmdlIGFtb3VudCBvZiB2YXJpYXRpb24gaW4gb3VyIG91dGNvbWUgdmFyaWFibGUuIApVc2luZyB0aGlzIG1vZGVsLCB3ZSBjYW4gdGhlbiBwcmVkaWN0IHRoZSBvdXRjb21lIGZyb20gbmV3IG9ic2VydmF0aW9ucyB3aXRoIHRoZSBzYW1lIGZlYXR1cmVzIHdoZXJlIGhhdmUgbm90IG9ic2VydmVkIHRoZSBvdXRjb21lLiAKCkFzIGEgc2ltcGxlIGV4YW1wbGUsIGltYWdpbmUgdGhhdCB3ZSBoYXZlIGRhdGEgYWJvdXQgdGhlIHNhbGVzIGFuZCBjaGFyYWN0ZXJpc3RpY3Mgb2YgY2FycyBmcm9tIGxhc3QgeWVhciBhbmQgd2Ugd2FudCB0byBwcmVkaWN0IHdoaWNoIGNhcnMgbWlnaHQgc2VsbCB3ZWxsIHRoaXMgeWVhci4gCldlIGRvIG5vdCBoYXZlIHRoZSBzYWxlcyBkYXRhIHlldCBmb3IgdGhpcyB5ZWFyLCBidXQgd2UgZG8ga25vdyB0aGUgY2hhcmFjdGVyaXN0aWNzIG9mIG91ciBjYXJzIGZvciB0aGlzIHllYXIuIApXZSBjYW4gYnVpbGQgYSBtb2RlbCBvZiB0aGUgY2hhcmFjdGVyaXN0aWNzIHRoYXQgZXhwbGFpbmVkIHNhbGVzIGxhc3QgeWVhciB0byBlc3RpbWF0ZSB3aGF0IGNhcnMgbWlnaHQgc2VsbCB3ZWxsIHRoaXMgeWVhci4gCkluIHRoaXMgY2FzZSwgb3VyIG91dGNvbWUgdmFyaWFibGUgaXMgdGhlIHNhbGVzIG9mIGNhcnMsIHdoaWxlIHRoZSBkaWZmZXJlbnQgY2hhcmFjdGVyaXN0aWNzIG9mIHRoZSBjYXJzIG1ha2UgdXAgb3VyIGZlYXR1cmVzLgoKIyMjIFN0YXJ0IHdpdGggYSBxdWVzdGlvbgoKVGhpcyBpcyB0aGUgbW9zdCBjb21tb25seSBtaXNzZWQgc3RlcCB3aGVuIGRldmVsb3BpbmcgYSBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobS4gCk1hY2hpbmUgbGVhcm5pbmcgY2FuIHZlcnkgZWFzaWx5IGJlIHR1cm5lZCBpbnRvIGFuIGVuZ2luZWVyaW5nIHByb2JsZW0uIApKdXN0IGR1bXAgdGhlIG91dGNvbWUgYW5kIHRoZSBmZWF0dXJlcyBpbnRvIGEgYmxhY2sgYm94IGFsZ29yaXRobSBhbmQgdmlvbGEhIApCdXQgdGhpcyBraW5kIG9mIHRoaW5raW5nIGNhbiBsZWFkIHRvIG1ham9yIHByb2JsZW1zLiBJbiBnZW5lcmFsIGdvb2QgbWFjaGluZSBsZWFybmluZyBxdWVzdGlvbnM6CgoxLiBIYXZlIGEgcGxhdXNpYmxlIGV4cGxhbmF0aW9uIGZvciB3aHkgdGhlIGZlYXR1cmVzIHByZWRpY3QgdGhlIG91dGNvbWUuIAoyLiBDb25zaWRlciBwb3RlbnRpYWwgdmFyaWF0aW9uIGluIGJvdGggdGhlIGZlYXR1cmVzIGFuZCB0aGUgb3V0Y29tZSBvdmVyIHRpbWUKMy4gQXJlIGNvbnNpc3RlbnRseSByZS1ldmFsdWF0ZWQgb24gY3JpdGVyaWEgMSBhbmQgMiBvdmVyIHRpbWUuIAoKSW4gdGhpcyBjYXNlIHN0dWR5LCB3ZSB3YW50IHRvICoqcHJlZGljdCoqIGFpciBwb2xsdXRpb24gbGV2ZWxzLiAKVG8gYnVpbGQgdGhpcyBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobSwgb3VyICoqb3V0Y29tZSB2YXJpYWJsZSoqIGlzIGZpbmUgcGFydGljdWxhdGUgbWF0dGVyIChQTX4yLjV+KSBjYXB0dXJlZCBmcm9tIGFpciBwb2xsdXRpb24gbW9uaXRvcnMgaW4gdGhlIGNvbnRpZ3VvdXMgVVMgZnJvbSAyMDA4LiAKT3VyICoqZmVhdHVyZXMqKiAob3IgcHJlZGljdG9yIHZhcmlhYmxlcykgaW5jbHVkZSBkYXRhIGFib3V0IHBvcHVsYXRpb24gZGVuc2l0eSwgcm9hZCBkZW5zaXR5LCB1cmJhbml6YXRpb24gbGV2ZWxzLCBhbmQgTkFTQSBzYXRlbGxpdGUgZGF0YS4gCgpBbGwgb2Ygb3VyIGRhdGEgd2FzIHByZXZpb3VzbHkgY29sbGVjdGVkIGJ5IGEgW3Jlc2VhcmNoZXJdKGh0dHA6Ly93d3cuYmlvc3RhdC5qaHNwaC5lZHUvfnJwZW5nLykgYXQgdGhlIFtKb2hucyBIb3BraW5zIFNjaG9vbCBvZiBQdWJsaWMgSGVhbHRoXShodHRwczovL3d3dy5qaHNwaC5lZHUvKSB3aG8gc3R1ZGllcyBhaXIgcG9sbHV0aW9uIGFuZCBjbGltYXRlIGNoYW5nZS4gCgoKIyMjIE91ciBvdXRjb21lIHZhcmlhYmxlCgpUaGUgbW9uaXRvciBkYXRhIHRoYXQgd2Ugd2lsbCBiZSB1c2luZyBjb21lcyBmcm9tICoqW2dyYXZpbWV0cmljIG1vbml0b3JzXShodHRwczovL3B1YmxpY2xhYi5vcmcvd2lraS9maWx0ZXItcG0pe3RhcmdldD0iX2JsYW5rIn0qKiAoc2VlIHBpY3R1cmUgYmVsb3cpIG9wZXJhdGVkIGJ5IHRoZSBVUyBbRW5pdm9ybm1lbnRhbCBQcm90ZWN0aW9uIEFnZW5jeSAoRVBBKV0oaHR0cHM6Ly93d3cuZXBhLmdvdi8pe3RhcmdldD0iX2JsYW5rIn0uCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGg9IjEwMHB4In0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwibW9uaXRvci5wbmciKSkKYGBgCgpUaGVzZSBtb25pdG9ycyB1c2UgYSBmaWx0cmF0aW9uIHN5c3RlbSB0byBzcGVjaWZpY2FsbHkgY2FwdHVyZSBmaW5lIHBhcnRpY3VsYXRlIG1hdHRlci4gCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGg9IjE1MHB4In0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwiZmlsdGVyLnBuZyIpKQpgYGAKCiMjIyMjIFtbc291cmNlXV0oaHR0cHM6Ly9wdWJsaWNsYWIub3JnL3dpa2kvZmlsdGVyLXBtKXt0YXJnZXQ9Il9ibGFuayJ9CgpUaGUgd2VpZ2h0IG9mIHRoaXMgcGFydGljdWxhdGUgbWF0dGVyIGlzIG1hbnVhbGx5IG1lYXN1cmVkIGRhaWx5IG9yIHdlZWtseS4gCkZvciB0aGUgRVBBIHN0YW5kYXJkIG9wZXJhdGluZyBwcm9jZWR1cmUgZm9yIFBNIGdyYXZpbWV0cmljIGFuYWx5c2lzIGluIDIwMDgsIHdlIHJlZmVyIHRoZSByZWFkZXIgdG8gW2hlcmVdKGh0dHBzOi8vd3d3My5lcGEuZ292L3R0bmFtdGkxL2ZpbGVzL2FtYmllbnQvcG0yNS9zcGVjL1JUSUdyYXZNYXNzU09QRklOQUwucGRmKXt0YXJnZXQ9Il9ibGFuayJ9LgoKPGRldGFpbHM+PHN1bW1hcnk+Rm9yIG1vcmUgb24gR3JhdmltZXRyaWMgYW5hbHlzaXMsIHlvdSBjYW4gZXhwYW5kIGhlcmUgPC9zdW1tYXJ5PgoKR3JhdmltZXRyaWMgYW5hbHlzaXMgaXMgYWxzbyB1c2VkIGZvciBbZW1pc3Npb24gdGVzdGluZ10oaHR0cHM6Ly93d3cubXQuY29tL3VzL2VuL2hvbWUvYXBwbGljYXRpb25zL0xhYm9yYXRvcnlfd2VpZ2hpbmcvZW1pc3Npb25zLXRlc3RpbmctcGFydGljdWxhdGUtbWF0dGVyLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0uIApUaGUgc2FtZSBpZGVhIGFwcGxpZXM6IGEgZnJlc2ggZmlsdGVyIGlzIGFwcGxpZWQgYW5kIHRoZSBkZXNpcmVkIGFtb3VudCBvZiB0aW1lIHBhc3NlcywgdGhlbiB0aGUgZmlsdGVyIGlzIHJlbW92ZWQgYW5kIHdlaWdoZWQuIAoKVGhlcmUgYXJlIFtvdGhlciBtb25pdG9yaW5nIHN5c3RlbXNdKGh0dHBzOi8vd3d3LnNlbnNpcmlvbi5jb20vZW4vYWJvdXQtdXMvbmV3c3Jvb20vc2Vuc2lyaW9uLXNwZWNpYWxpc3QtYXJ0aWNsZXMvcGFydGljdWxhdGUtbWF0dGVyLXNlbnNpbmctZm9yLWFpci1xdWFsaXR5LW1lYXN1cmVtZW50cy8pe3RhcmdldD0iX2JsYW5rIn0gdGhhdCBjYW4gcHJvdmlkZSBob3VybHkgbWVhc3VyZW1lbnRzLCBidXQgd2Ugd2lsbCBub3QgYmUgdXNpbmcgZGF0YSBmcm9tIHRoZXNlIG1vbml0b3JzIGluIG91ciBhbmFseXNpcy4gCkdyYXZpbWV0cmljIGFuYWx5c2lzIGlzIGNvbnNpZGVyZWQgdG8gYmUgYW1vbmcgdGhlIG1vc3QgYWNjdXJhdGUgbWV0aG9kcyBmb3IgbWVhc3VyaW5nIHBhcnRpY3VsYXRlIG1hdHRlci4KCjwvZGV0YWlscz4KCkluIG91ciBkYXRhIHNldCwgdGhlIGB2YWx1ZWAgY29sdW1uIGluZGljYXRlcyB0aGUgUE1+Mi41fiBtb25pdG9yIGF2ZXJhZ2UgZm9yIDIwMDggaW4gbWFzcyBvZiBmaW5lIHBhcnRpY2xlcy92b2x1bWUgb2YgYWlyIGZvciA4NzYgZ3JhdmltZXRyaWMgbW9uaXRvcnMuIApUaGUgdW5pdHMgYXJlIG1pY3JvZ3JhbXMgb2YgZmluZSBwYXJ0aWN1bGF0ZSBtYXRlciAoUE0pIHRoYXQgaXMgbGVzcyB0aGFuIDIuNSBtaWNyb21ldGVycyBpbiBkaWFtZXRlciBwZXIgY3ViaWMgbWV0ZXIgb2YgYWlyIC0gbWFzcyBjb25jZW50cmF0aW9uICh1Zy9tXjNeKS4KUmVjYWxsIHRoZSBXSE8gZXhwb3N1cmUgZ3VpZGVsaW5lIGlzIDwgMTAgdWcvbV4zXiBvbiBhdmVyYWdlIGFubnVhbGx5IGZvciBQTX4yLjV+LgoKIyMjIE91ciBmZWF0dXJlcyAocHJlZGljdG9yIHZhcmlhYmxlcykgCgpUaGVyZSBhcmUgNDggZmVhdHVyZXMgd2l0aCB2YWx1ZXMgZm9yIGVhY2ggb2YgdGhlIDg3NiBtb25pdG9ycyAob2JzZXJ2YXRpb25zKS4gClRoZSBkYXRhIGNvbWVzIGZyb20gdGhlIFVTIFtFbml2b3JubWVudGFsIFByb3RlY3Rpb24gQWdlbmN5IChFUEEpXShodHRwczovL3d3dy5lcGEuZ292Lyl7dGFyZ2V0PSJfYmxhbmsifSwgdGhlIFtOYXRpb25hbCBBZXJvbmF1dGljcyBhbmQgU3BhY2UgQWRtaW5pc3RyYXRpb24gKE5BU0EpXShodHRwczovL3d3dy5uYXNhLmdvdi8pe3RhcmdldD0iX2JsYW5rIn0sIHRoZSBVUyBbQ2Vuc3VzXShodHRwczovL3d3dy5jZW5zdXMuZ292L2Fib3V0L3doYXQvY2Vuc3VzLWF0LWEtZ2xhbmNlLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0sIGFuZCB0aGUgW05hdGlvbmFsIENlbnRlciBmb3IgSGVhbHRoIFN0YXRpc3RpY3MgKE5DSFMpXShodHRwczovL3d3dy5jZGMuZ292L25jaHMvYWJvdXQvaW5kZXguaHRtKXt0YXJnZXQ9Il9ibGFuayJ9LgoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIGhlcmUgdG8gc2VlIGEgdGFibGUgYWJvdXQgdGhlIHNldCBvZiBmZWF0dXJlcyA8L3N1bW1hcnk+CgpWYXJpYWJsZSAgIHwgRGV0YWlscyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAotLS0tLS0tLS0tIHwtLS0tLS0tLS0tLS0tCioqaWQqKiAgfCBNb25pdG9yIG51bWJlciAgPGJyPiAtLSB0aGUgY291bnR5IG51bWJlciBpcyBpbmRpY2F0ZWQgYmVmb3JlIHRoZSBkZWNpbWFsIDxicj4gLS0gdGhlIG1vbml0b3IgbnVtYmVyIGlzIGluZGljYXRlZCBhZnRlciB0aGUgZGVjaW1hbCA8YnI+ICAqKkV4YW1wbGUqKjogMTA3My4wMDIzICBpcyBKZWZmZXJzb24gY291bnR5ICgxMDczKSBhbmQgLjAwMjMgb25lIG9mIDggbW9uaXRvcnMgCioqZmlwcyoqIHwgRmVkZXJhbCBpbmZvcm1hdGlvbiBwcm9jZXNzaW5nIHN0YW5kYXJkIG51bWJlciBmb3IgdGhlIGNvdW50eSB3aGVyZSB0aGUgbW9uaXRvciBpcyBsb2NhdGVkIDxicj4gLS0gNSBkaWdpdCBpZCBjb2RlIGZvciBjb3VudGllcyAoemVybyBpcyBvZnRlbiB0aGUgZmlyc3QgdmFsdWUgYW5kIHNvbWV0aW1lcyBpcyBub3Qgc2hvd24pIDxicj4gLS0gdGhlIGZpcnN0IDIgbnVtYmVycyBpbmRpY2F0ZSB0aGUgc3RhdGUgPGJyPiAtLSB0aGUgbGFzdCB0aHJlZSBudW1iZXJzIGluZGljYXRlIHRoZSBjb3VudHkgPGJyPiAgKipFeGFtcGxlKio6IEFsYWJhbWEncyBzdGF0ZSBjb2RlIGlzIDAxIGJlY2F1c2UgaXQgaXMgZmlyc3QgYWxwaGFiZXRpY2FsbHkgPGJyPiAobm90ZTogQWxhc2thIGFuZCBIYXdhaWkgYXJlIG5vdCBpbmNsdWRlZCBiZWNhdXNlIHRoZXkgYXJlIG5vdCBwYXJ0IG9mIHRoZSBjb250aWd1b3VzIFVTKSAgCioqTGF0KiogfCBMYXRpdHVkZSBvZiB0aGUgbW9uaXRvciBpbiBkZWdyZWVzICAKKipMb24qKiB8IExvbmdpdHVkZSBvZiB0aGUgbW9uaXRvciBpbiBkZWdyZWVzICAKKipzdGF0ZSoqIHwgU3RhdGUgd2hlcmUgdGhlIG1vbml0b3IgaXMgbG9jYXRlZAoqKmNvdW50eSoqIHwgQ291bnR5IHdoZXJlIHRoZSBtb25pdG9yIGlzIGxvY2F0ZWQKKipjaXR5KiogfCBDaXR5IHdoZXJlIHRoZSBtb25pdG9yIGlzIGxvY2F0ZWQKKipDTUFRKiogIHwgRXN0aW1hdGVkIHZhbHVlcyBvZiBhaXIgcG9sbHV0aW9uIGZyb20gYSBjb21wdXRhdGlvbmFsIG1vZGVsIGNhbGxlZCBbKipDb21tdW5pdHkgTXVsdGlzY2FsZSBBaXIgUXVhbGl0eSAoQ01BUSkqKl0oaHR0cHM6Ly93d3cuZXBhLmdvdi9jbWFxKXt0YXJnZXQ9Il9ibGFuayJ9IDxicj4gLS0gIEEgbW9uaXRvcmluZyBzeXN0ZW0gdGhhdCBzaW11bGF0ZXMgdGhlIHBoeXNpY3Mgb2YgdGhlIGF0bW9zcGhlcmUgdXNpbmcgY2hlbWlzdHJ5IGFuZCB3ZWF0aGVyIGRhdGEgdG8gcHJlZGljdCB0aGUgYWlyIHBvbGx1dGlvbiA8YnI+IC0tICoqKkRvZXMgbm90IHVzZSBhbnkgb2YgdGhlIFBNfjIuNX4gZ3JhdmltZXRyaWMgbW9uaXRvcmluZyBkYXRhLioqKiAoVGhlcmUgaXMgYSB2ZXJzaW9uIHRoYXQgZG9lcyB1c2UgdGhlIGdyYXZpbWV0cmljIG1vbml0b3JpbmcgZGF0YSwgYnV0IG5vdCB0aGlzIG9uZSEpIDxicj4gLS0gRGF0YSBmcm9tIHRoZSBFUEEKKip6Y3RhKiogfCBbWmlwIENvZGUgVGFidWxhdGlvbiBBcmVhXShodHRwczovL3d3dzIuY2Vuc3VzLmdvdi9nZW8vcGRmcy9lZHVjYXRpb24vYnJvY2h1cmVzL1pDVEFzLnBkZil7dGFyZ2V0PSJfYmxhbmsifSB3aGVyZSB0aGUgbW9uaXRvciBpcyBsb2NhdGVkIDxicj4gLS0gUG9zdGFsIFppcCBjb2RlcyBhcmUgY29udmVydGVkIGludG8gImdlbmVyYWxpemVkIGFyZWFsIHJlcHJlc2VudGF0aW9ucyIgdGhhdCBhcmUgbm9uLW92ZXJsYXBwaW5nICA8YnI+IC0tIERhdGEgZnJvbSB0aGUgMjAxMCBDZW5zdXMgIAoqKnpjdGFfYXJlYSoqIHwgTGFuZCBhcmVhIG9mIHRoZSB6aXAgY29kZSBhcmVhIGluIG1ldGVycyBzcXVhcmVkICA8YnI+IC0tIERhdGEgZnJvbSB0aGUgMjAxMCBDZW5zdXMgIAoqKnpjdGFfcG9wKiogfCBQb3B1bGF0aW9uIGluIHRoZSB6aXAgY29kZSBhcmVhICA8YnI+IC0tIERhdGEgZnJvbSB0aGUgMjAxMCBDZW5zdXMgIAoqKmltcF9hNTAwKiogfCBJbXBlcnZpb3VzIHN1cmZhY2UgbWVhc3VyZSA8YnI+IC0tIFdpdGhpbiBhIGNpcmNsZSB3aXRoIGEgcmFkaXVzIG9mIDUwMCBtZXRlcnMgYXJvdW5kIHRoZSBtb25pdG9yIDxicj4gLS0gSW1wZXJ2aW91cyBzdXJmYWNlIGFyZSByb2FkcywgY29uY3JldGUsIHBhcmtpbmcgbG90cywgYnVpbGRpbmdzIDxicj4gLS0gVGhpcyBpcyBhIG1lYXN1cmUgb2YgZGV2ZWxvcG1lbnQgCioqaW1wX2ExMDAwKiogfCBJbXBlcnZpb3VzIHN1cmZhY2UgbWVhc3VyZSA8YnI+IC0tICBXaXRoaW4gYSBjaXJjbGUgd2l0aCBhIHJhZGl1cyBvZiAxMDAwIG1ldGVycyBhcm91bmQgdGhlIG1vbml0b3IKKippbXBfYTUwMDAqKiB8IEltcGVydmlvdXMgc3VyZmFjZSBtZWFzdXJlIDxicj4gLS0gIFdpdGhpbiBhIGNpcmNsZSB3aXRoIGEgcmFkaXVzIG9mIDUwMDAgbWV0ZXJzIGFyb3VuZCB0aGUgbW9uaXRvciAgCioqaW1wX2ExMDAwMCoqIHwgSW1wZXJ2aW91cyBzdXJmYWNlIG1lYXN1cmUgPGJyPiAtLSAgV2l0aGluIGEgY2lyY2xlIHdpdGggYSByYWRpdXMgb2YgMTAwMDAgbWV0ZXJzIGFyb3VuZCB0aGUgbW9uaXRvciAgIAoqKmltcF9hMTUwMDAqKiB8IEltcGVydmlvdXMgc3VyZmFjZSBtZWFzdXJlIDxicj4gLS0gIFdpdGhpbiBhIGNpcmNsZSB3aXRoIGEgcmFkaXVzIG9mIDE1MDAwIG1ldGVycyBhcm91bmQgdGhlIG1vbml0b3IgIAoqKmNvdW50eV9hcmVhKiogfCBMYW5kIGFyZWEgb2YgdGhlIGNvdW50eSBvZiB0aGUgbW9uaXRvciBpbiBtZXRlcnMgc3F1YXJlZCAgCioqY291bnR5X3BvcCoqIHwgUG9wdWxhdGlvbiBvZiB0aGUgY291bnR5IG9mIHRoZSBtb25pdG9yICAKKipMb2dfZGlzdF90b19wcmlzZWMqKiB8IExvZyAoTmF0dXJhbCBsb2cpIGRpc3RhbmNlIHRvIGEgcHJpbWFyeSBvciBzZWNvbmRhcnkgcm9hZCBmcm9tIHRoZSBtb25pdG9yIDxicj4gLS0gSGlnaHdheSBvciBtYWpvciByb2FkICAKKipsb2dfcHJpX2xlbmd0aF81MDAwKiogfCBDb3VudCBvZiBwcmltYXJ5IHJvYWQgbGVuZ3RoIGluIG1ldGVycyBpbiBhIGNpcmNsZSB3aXRoIGEgcmFkaXVzIG9mIDUwMDAgbWV0ZXJzIGFyb3VuZCB0aGUgbW9uaXRvciAoTmF0dXJhbCBsb2cpIDxicj4gLS0gSGlnaHdheXMgb25seSAgCioqbG9nX3ByaV9sZW5ndGhfMTAwMDAqKiB8IENvdW50IG9mIHByaW1hcnkgcm9hZCBsZW5ndGggaW4gbWV0ZXJzIGluIGEgY2lyY2xlIHdpdGggYSByYWRpdXMgb2YgMTAwMDAgbWV0ZXJzIGFyb3VuZCB0aGUgbW9uaXRvciAoTmF0dXJhbCBsb2cpIDxicj4gLS0gSGlnaHdheXMgb25seSAgCioqbG9nX3ByaV9sZW5ndGhfMTUwMDAqKiB8IENvdW50IG9mIHByaW1hcnkgcm9hZCBsZW5ndGggaW4gbWV0ZXJzIGluIGEgY2lyY2xlIHdpdGggYSByYWRpdXMgb2YgMTUwMDAgbWV0ZXJzIGFyb3VuZCB0aGUgbW9uaXRvciAoTmF0dXJhbCBsb2cpIDxicj4gLS0gSGlnaHdheXMgb25seSAgCioqbG9nX3ByaV9sZW5ndGhfMjUwMDAqKiB8IENvdW50IG9mIHByaW1hcnkgcm9hZCBsZW5ndGggaW4gbWV0ZXJzIGluIGEgY2lyY2xlIHdpdGggYSByYWRpdXMgb2YgMjUwMDAgbWV0ZXJzIGFyb3VuZCB0aGUgbW9uaXRvciAoTmF0dXJhbCBsb2cpIDxicj4gLS0gSGlnaHdheXMgb25seSAgCioqbG9nX3ByaXNlY19sZW5ndGhfNTAwKiogfCBDb3VudCBvZiBwcmltYXJ5IGFuZCBzZWNvbmRhcnkgcm9hZCBsZW5ndGggaW4gbWV0ZXJzIGluIGEgY2lyY2xlIHdpdGggYSByYWRpdXMgb2YgNTAwIG1ldGVycyBhcm91bmQgdGhlIG1vbml0b3IgKE5hdHVyYWwgbG9nKSAgPGJyPiAtLSBIaWdod2F5IGFuZCBzZWNvbmRhcnkgcm9hZHMgIAoqKmxvZ19wcmlzZWNfbGVuZ3RoXzEwMDAqKiB8IENvdW50IG9mIHByaW1hcnkgYW5kIHNlY29uZGFyeSByb2FkIGxlbmd0aCBpbiBtZXRlcnMgaW4gYSBjaXJjbGUgd2l0aCBhIHJhZGl1cyBvZiAxMDAwIG1ldGVycyBhcm91bmQgdGhlIG1vbml0b3IgKE5hdHVyYWwgbG9nKSAgPGJyPiAtLSBIaWdod2F5IGFuZCBzZWNvbmRhcnkgcm9hZHMgIAoqKmxvZ19wcmlzZWNfbGVuZ3RoXzUwMDAqKiB8IENvdW50IG9mIHByaW1hcnkgYW5kIHNlY29uZGFyeSByb2FkIGxlbmd0aCBpbiBtZXRlcnMgaW4gYSBjaXJjbGUgd2l0aCBhIHJhZGl1cyBvZiA1MDAwIG1ldGVycyBhcm91bmQgdGhlIG1vbml0b3IgKE5hdHVyYWwgbG9nKSAgPGJyPiAtLSBIaWdod2F5IGFuZCBzZWNvbmRhcnkgcm9hZHMgIAoqKmxvZ19wcmlzZWNfbGVuZ3RoXzEwMDAwKiogfCBDb3VudCBvZiBwcmltYXJ5IGFuZCBzZWNvbmRhcnkgcm9hZCBsZW5ndGggaW4gbWV0ZXJzIGluIGEgY2lyY2xlIHdpdGggYSByYWRpdXMgb2YgMTAwMDAgbWV0ZXJzIGFyb3VuZCB0aGUgbW9uaXRvciAoTmF0dXJhbCBsb2cpICA8YnI+IC0tIEhpZ2h3YXkgYW5kIHNlY29uZGFyeSByb2FkcyAgCioqbG9nX3ByaXNlY19sZW5ndGhfMTUwMDAqKiB8IENvdW50IG9mIHByaW1hcnkgYW5kIHNlY29uZGFyeSByb2FkIGxlbmd0aCBpbiBtZXRlcnMgaW4gYSBjaXJjbGUgd2l0aCBhIHJhZGl1cyBvZiAxNTAwMCBtZXRlcnMgYXJvdW5kIHRoZSBtb25pdG9yIChOYXR1cmFsIGxvZykgIDxicj4gLS0gSGlnaHdheSBhbmQgc2Vjb25kYXJ5IHJvYWRzICAKKipsb2dfcHJpc2VjX2xlbmd0aF8yNTAwMCoqIHwgQ291bnQgb2YgcHJpbWFyeSBhbmQgc2Vjb25kYXJ5IHJvYWQgbGVuZ3RoIGluIG1ldGVycyBpbiBhIGNpcmNsZSB3aXRoIGEgcmFkaXVzIG9mIDI1MDAwIG1ldGVycyBhcm91bmQgdGhlIG1vbml0b3IgKE5hdHVyYWwgbG9nKSAgPGJyPiAtLSBIaWdod2F5IGFuZCBzZWNvbmRhcnkgcm9hZHMgICAgICAKKipsb2dfbmVpXzIwMDhfcG0yNV9zdW1fMTAwMDAqKiB8IFRvbnMgb2YgZW1pc3Npb25zIGZyb20gbWFqb3Igc291cmNlcyBkYXRhIGJhc2UgKGFubnVhbCBkYXRhKSBzdW0gb2YgYWxsIHNvdXJjZXMgd2l0aGluIGEgY2lyY2xlIHdpdGggYSByYWRpdXMgb2YgMTAwMDAgbWV0ZXJzIG9mIGRpc3RhbmNlIGFyb3VuZCB0aGUgbW9uaXRvciAoTmF0dXJhbCBsb2cpICAgIAoqKmxvZ19uZWlfMjAwOF9wbTI1X3N1bV8xNTAwMCoqIHwgVG9ucyBvZiBlbWlzc2lvbnMgZnJvbSBtYWpvciBzb3VyY2VzIGRhdGEgYmFzZSAoYW5udWFsIGRhdGEpIHN1bSBvZiBhbGwgc291cmNlcyB3aXRoaW4gYSBjaXJjbGUgd2l0aCBhIHJhZGl1cyBvZiAxNTAwMCBtZXRlcnMgb2YgZGlzdGFuY2UgYXJvdW5kIHRoZSBtb25pdG9yIChOYXR1cmFsIGxvZykgICAgIAoqKmxvZ19uZWlfMjAwOF9wbTI1X3N1bV8yNTAwMCoqIHwgVG9ucyBvZiBlbWlzc2lvbnMgZnJvbSBtYWpvciBzb3VyY2VzIGRhdGEgYmFzZSAoYW5udWFsIGRhdGEpIHN1bSBvZiBhbGwgc291cmNlcyB3aXRoaW4gYSBjaXJjbGUgd2l0aCBhIHJhZGl1cyBvZiAyNTAwMCBtZXRlcnMgb2YgZGlzdGFuY2UgYXJvdW5kIHRoZSBtb25pdG9yIChOYXR1cmFsIGxvZykgICAgIAoqKmxvZ19uZWlfMjAwOF9wbTEwX3N1bV8xMDAwMCoqIHwgVG9ucyBvZiBlbWlzc2lvbnMgZnJvbSBtYWpvciBzb3VyY2VzIGRhdGEgYmFzZSAoYW5udWFsIGRhdGEpIHN1bSBvZiBhbGwgc291cmNlcyB3aXRoaW4gYSBjaXJjbGUgd2l0aCBhIHJhZGl1cyBvZiAxMDAwMCBtZXRlcnMgb2YgZGlzdGFuY2UgYXJvdW5kIHRoZSBtb25pdG9yIChOYXR1cmFsIGxvZykgICAgICAKKipsb2dfbmVpXzIwMDhfcG0xMF9zdW1fMTUwMDAqKnwgVG9ucyBvZiBlbWlzc2lvbnMgZnJvbSBtYWpvciBzb3VyY2VzIGRhdGEgYmFzZSAoYW5udWFsIGRhdGEpIHN1bSBvZiBhbGwgc291cmNlcyB3aXRoaW4gYSBjaXJjbGUgd2l0aCBhIHJhZGl1cyBvZiAxNTAwMCBtZXRlcnMgb2YgZGlzdGFuY2UgYXJvdW5kIHRoZSBtb25pdG9yIChOYXR1cmFsIGxvZykgICAgICAKKipsb2dfbmVpXzIwMDhfcG0xMF9zdW1fMjUwMDAqKiB8IFRvbnMgb2YgZW1pc3Npb25zIGZyb20gbWFqb3Igc291cmNlcyBkYXRhIGJhc2UgKGFubnVhbCBkYXRhKSBzdW0gb2YgYWxsIHNvdXJjZXMgd2l0aGluIGEgY2lyY2xlIHdpdGggYSByYWRpdXMgb2YgMjUwMDAgbWV0ZXJzIG9mIGRpc3RhbmNlIGFyb3VuZCB0aGUgbW9uaXRvciAoTmF0dXJhbCBsb2cpICAgICAgCioqcG9wZGVuc19jb3VudHkqKiB8IFBvcHVsYXRpb24gZGVuc2l0eSAobnVtYmVyIG9mIHBlb3BsZSBwZXIga2lsb21ldGVyIHNxdWFyZWQgYXJlYSBvZiB0aGUgY291bnR5KQoqKnBvcGRlbnNfemN0YSoqIHwgUG9wdWxhdGlvbiBkZW5zaXR5IChudW1iZXIgb2YgcGVvcGxlIHBlciBraWxvbWV0ZXIgc3F1YXJlZCBhcmVhIG9mIHpjdGEpCioqbm9ocyoqIHwgUGVyY2VudGFnZSBvZiBwZW9wbGUgaW4gemN0YSBhcmVhIHdoZXJlIHRoZSBtb25pdG9yIGlzIHRoYXQgKipkbyBub3QgaGF2ZSBhIGhpZ2ggc2Nob29sIGRlZ3JlZSoqIDxicj4gLS0gRGF0YSBmcm9tIHRoZSBDZW5zdXMKKipzb21laHMqKiB8IFBlcmNlbnRhZ2Ugb2YgcGVvcGxlIGluIHpjdGEgYXJlYSB3aGVyZSB0aGUgbW9uaXRvciB3aG9zZSBoaWdoZXN0IGZvcm1hbCBlZHVjYXRpb25hbCBhdHRhaW5tZW50IHdhcyAqKnNvbWUgaGlnaCBzY2hvb2wgZWR1Y2F0aW9uKiogPGJyPiAtLSBEYXRhIGZyb20gdGhlIENlbnN1cwoqKmhzKiogfCBQZXJjZW50YWdlIG9mIHBlb3BsZSBpbiB6Y3RhIGFyZWEgd2hlcmUgdGhlIG1vbml0b3Igd2hvc2UgaGlnaGVzdCBmb3JtYWwgZWR1Y2F0aW9uYWwgYXR0YWlubWVudCB3YXMgY29tcGxldGluZyBhICoqaGlnaCBzY2hvb2wgZGVncmVlKiogPGJyPiAtLSBEYXRhIGZyb20gdGhlIENlbnN1cyAgCioqc29tZWNvbGxlZ2UqKiB8IFBlcmNlbnRhZ2Ugb2YgcGVvcGxlIGluIHpjdGEgYXJlYSB3aGVyZSB0aGUgbW9uaXRvciB3aG9zZSBoaWdoZXN0IGZvcm1hbCBlZHVjYXRpb25hbCBhdHRhaW5tZW50IHdhcyBjb21wbGV0aW5nICoqc29tZSBjb2xsZWdlIGVkdWNhdGlvbioqIDxicj4gLS0gRGF0YSBmcm9tIHRoZSBDZW5zdXMgCioqYXNzb2NpYXRlKiogfCBQZXJjZW50YWdlIG9mIHBlb3BsZSBpbiB6Y3RhIGFyZWEgd2hlcmUgdGhlIG1vbml0b3Igd2hvc2UgaGlnaGVzdCBmb3JtYWwgZWR1Y2F0aW9uYWwgYXR0YWlubWVudCB3YXMgY29tcGxldGluZyBhbiAqKmFzc29jaWF0ZSBkZWdyZWUqKiA8YnI+IC0tIERhdGEgZnJvbSB0aGUgQ2Vuc3VzIAoqKmJhY2hlbG9yKiogfCBQZXJjZW50YWdlIG9mIHBlb3BsZSBpbiB6Y3RhIGFyZWEgd2hlcmUgdGhlIG1vbml0b3Igd2hvc2UgaGlnaGVzdCBmb3JtYWwgZWR1Y2F0aW9uYWwgYXR0YWlubWVudCB3YXMgYSAqKmJhY2hlbG9yJ3MgZGVncmVlKiogPGJyPiAtLSBEYXRhIGZyb20gdGhlIENlbnN1cyAKKipncmFkKiogfCBQZXJjZW50YWdlIG9mIHBlb3BsZSBpbiB6Y3RhIGFyZWEgd2hlcmUgdGhlIG1vbml0b3Igd2hvc2UgaGlnaGVzdCBmb3JtYWwgZWR1Y2F0aW9uYWwgYXR0YWlubWVudCB3YXMgYSAqKmdyYWR1YXRlIGRlZ3JlZSoqIDxicj4gLS0gRGF0YSBmcm9tIHRoZSBDZW5zdXMgCioqcG92KiogfCBQZXJjZW50YWdlIG9mIHBlb3BsZSBpbiB6Y3RhIGFyZWEgd2hlcmUgdGhlIG1vbml0b3IgaXMgdGhhdCBsaXZlZCBpbiBbKipwb3ZlcnR5KipdKGh0dHBzOi8vYXNwZS5oaHMuZ292LzIwMDgtaGhzLXBvdmVydHktZ3VpZGVsaW5lcykgaW4gMjAwOCAtIG9yIHdvdWxkIGl0IGhhdmUgYmVlbiAyMDA3IGd1aWRlbGluZXM/P2h0dHBzOi8vYXNwZS5oaHMuZ292LzIwMDctaGhzLXBvdmVydHktZ3VpZGVsaW5lcyA8YnI+IC0tIERhdGEgZnJvbSB0aGUgQ2Vuc3VzICAKKipoc19vcmxlc3MqKiB8ICBQZXJjZW50YWdlIG9mIHBlb3BsZSBpbiB6Y3RhIGFyZWEgd2hlcmUgdGhlIG1vbml0b3Igd2hvc2UgaGlnaGVzdCBmb3JtYWwgZWR1Y2F0aW9uYWwgYXR0YWlubWVudCB3YXMgYSAqKmhpZ2ggc2Nob29sIGRlZ3JlZSBvciBsZXNzKiogKHN1bSBvZiBub2hzLCBzb21laHMsIGFuZCBocykgIAoqKnVyYzIwMTMqKiB8IFsyMDEzIFVyYmFuLXJ1cmFsIGNsYXNzaWZpY2F0aW9uXShodHRwczovL3d3dy5jZGMuZ292L25jaHMvZGF0YS9zZXJpZXMvc3JfMDIvc3IwMl8xNjYucGRmKXt0YXJnZXQ9Il9ibGFuayJ9IG9mIHRoZSBjb3VudHkgd2hlcmUgdGhlIG1vbml0b3IgaXMgbG9jYXRlZCA8YnI+IC0tIDYgY2F0ZWdvcnkgdmFyaWFibGUgLSAxIGlzIHRvdGFsbHkgdXJiYW4gNiBpcyBjb21wbGV0ZWx5IHJ1cmFsIDxicj4gIC0tIERhdGEgZnJvbSB0aGUgTmF0aW9uYWwgQ2VudGVyIGZvciBIZWFsdGggU3RhdGlzdGljc10oaHR0cHM6Ly93d3cuY2RjLmdvdi9uY2hzL2luZGV4Lmh0bSl7dGFyZ2V0PSJfYmxhbmsifSAgICAgCioqdXJjMjAwNioqIHwgWzIwMDYgVXJiYW4tcnVyYWwgY2xhc3NpZmljYXRpb25dKGh0dHBzOi8vd3d3LmNkYy5nb3YvbmNocy9kYXRhL3Nlcmllcy9zcl8wMi9zcjAyXzE1NC5wZGYpe3RhcmdldD0iX2JsYW5rIn0gb2YgdGhlIGNvdW50eSB3aGVyZSB0aGUgbW9uaXRvciBpcyBsb2NhdGVkIDxicj4gLS0gNiBjYXRlZ29yeSB2YXJpYWJsZSAtIDEgaXMgdG90YWxseSB1cmJhbiA2IGlzIGNvbXBsZXRlbHkgcnVyYWwgPGJyPiAtLSBEYXRhIGZyb20gdGhlIFtOYXRpb25hbCBDZW50ZXIgZm9yIEhlYWx0aCBTdGF0aXN0aWNzXShodHRwczovL3d3dy5jZGMuZ292L25jaHMvaW5kZXguaHRtKXt0YXJnZXQ9Il9ibGFuayJ9ICAgICAKKiphb2QqKiB8IEFlcm9zb2wgT3B0aWNhbCBEZXB0aCBtZWFzdXJlbWVudCBmcm9tIGEgTkFTQSBzYXRlbGxpdGUgPGJyPiAtLSBiYXNlZCBvbiB0aGUgZGlmZnJhY3Rpb24gb2YgYSBsYXNlciA8YnI+IC0tIHVzZWQgYXMgYSBwcm94eSBvZiBwYXJ0aWN1bGF0ZSBwb2xsdXRpb24gPGJyPiAtLSB1bml0LWxlc3MgLSBoaWdoZXIgdmFsdWUgaW5kaWNhdGVzIG1vcmUgcG9sbHV0aW9uIDxicj4gLS0gRGF0YSBmcm9tIE5BU0EgIAoKPC9kZXRhaWxzPgoKTWFueSBvZiB0aGVzZSBmZWF0dXJlcyBoYXZlIHRvIGRvIHdpdGggdGhlIGNpcmN1bGFyIGFyZWEgYXJvdW5kIHRoZSBtb25pdG9yIGNhbGxlZCB0aGUgImJ1ZmZlciIuIFRoZXNlIGFyZSBpbGx1c3RyYXRlZCBpbiB0aGUgZm9sbG93aW5nIGZpZ3VyZToKCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI4MDBweCIsfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWciLCAicmVncmVzc2lvbi5wbmciKSkKYGBgCgojIyMjIyBbW3NvdXJjZV1dKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcHVibWVkLzE1MjkyOTA2KXt0YXJnZXQ9Il9ibGFuayJ9CgoKCiMgKipEYXRhIEltcG9ydCoqCioqKgoKQWxsIG9mIG91ciBkYXRhIHdhcyBwcmV2aW91c2x5IGNvbGxlY3RlZCBieSBhIFtyZXNlYXJjaGVyXShodHRwOi8vd3d3LmJpb3N0YXQuamhzcGguZWR1L35ycGVuZy8pIGF0IHRoZSBbSm9obnMgSG9wa2lucyBTY2hvb2wgb2YgUHVibGljIEhlYWx0aF0oaHR0cHM6Ly93d3cuamhzcGguZWR1Lykgd2hvIHN0dWRpZXMgYWlyIHBvbGx1dGlvbiBhbmQgY2xpbWF0ZSBjaGFuZ2UuIAoKV2UgaGF2ZSBvbmUgQ1NWIGZpbGUgdGhhdCBjb250YWlucyBib3RoIG91ciBzaW5nbGUgKipvdXRjb21lIHZhcmlhYmxlKiogYW5kIGFsbCBvZiBvdXIgKipmZWF0dXJlcyoqIChvciBwcmVkaWN0b3IgdmFyaWFibGVzKS4KCk5leHQsIHdlIGltcG9ydCBvdXIgZGF0YSBpbnRvIFIgbm93IHNvIHRoYXQgd2UgY2FuIGV4cGxvcmUgdGhlIGRhdGEgZnVydGhlci4gCldlIHdpbGwgY2FsbCBvdXIgZGF0YSBvYmplY3QgYHBtYCBmb3IgcGFydGljdWxhdGUgbWF0dGVyLiAKV2UgaW1wb3J0IHRoZSBkYXRhIHVzaW5nIHRoZSBgcmVhZF9jc3YoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYHJlYWRyYCBwYWNrYWdlLiAKYGBge3J9CnBtIDwtIHJlYWRyOjpyZWFkX2NzdihoZXJlKCJkb2NzIiwgInBtMjVfZGF0YS5jc3YiKSkKYGBgCgoKCiMgKipEYXRhIEV4cGxvcmF0aW9uIGFuZCBXcmFuZ2xpbmcqKgoqKioKClRoZSBmaXJzdCBzdGVwIGluIHBlcmZvcm1pbmcgYW55IGRhdGEgYW5hbHlzaXMgaXMgdG8gZXhwbG9yZSB0aGUgZGF0YS4gCgpGb3IgZXhhbXBsZSwgd2UgbWlnaHQgd2FudCB0byBiZXR0ZXIgdW5kZXJzdGFuZCB0aGUgdmFyaWFibGVzIGluY2x1ZGVkIGluIHRoZSBkYXRhLCBhcyB3ZSBtYXkgbGVhcm4gYWJvdXQgaW1wb3J0YW50IGRldGFpbHMgYWJvdXQgdGhlIGRhdGEgdGhhdCB3ZSBzaG91bGQga2VlcCBpbiBtaW5kIGFzIHdlIHRyeSB0byBwcmVkaWN0IG91ciBvdXRjb21lIHZhcmlhYmxlLgoKRmlyc3QsIGxldCdzIGp1c3QgZ2V0IGEgZ2VuZXJhbCBzZW5zZSBvZiBvdXIgZGF0YS4gCldlIGNhbiBkbyB0aGF0IHVzaW5nIHRoZSBgZ2xpbXBzZSgpYCBmdW5jdGlvbiBvZiB0aGUgYGRwbHlyYCBwYWNrYWdlIChpdCBpcyBhbHNvIGluIHRoZSBgdGliYmxlYCBwYWNrYWdlKS4KCldlIHdpbGwgYWxzbyB1c2UgdGhlIGAlPiVgIHBpcGUsIHdoaWNoIGNhbiBiZSB1c2VkIHRvIGRlZmluZSB0aGUgaW5wdXQgZm9yIGxhdGVyIHNlcXVlbnRpYWwgc3RlcHMuIAoKVGhpcyB3aWxsIG1ha2UgbW9yZSBzZW5zZSB3aGVuIHdlIGhhdmUgbXVsdGlwbGUgc2VxdWVudGlhbCBzdGVwcyB1c2luZyB0aGUgc2FtZSBkYXRhIG9iamVjdC4gCgpUbyB1c2UgdGhlIHBpcGUgbm90YXRpb24gd2UgbmVlZCB0byBpbnN0YWxsIGFuZCBsb2FkIGBkcGx5cmAgYXMgd2VsbC4KCkZvciBleGFtcGxlLCBoZXJlIHdlIHN0YXJ0IHdpdGggYHBtYCBkYXRhIG9iamVjdCBhbmQgInBpcGUiIHRoZSBvYmplY3QgaW50byBhcyBpbnB1dCBpbnRvIHRoZSBgZ2xpbXBzZSgpYCBmdW5jdGlvbi4gClRoZSBvdXRwdXQgaXMgaXMgYW4gb3ZlcnZpZXcgb2Ygd2hhdCBpcyBpbiB0aGUgYHBtYCBvYmplY3Qgc3VjaCBhcyB0aGUgbnVtYmVyIG9mIHJvd3MgYW5kIGNvbHVtbnMsIGFsbCB0aGUgY29sdW1uIG5hbWVzLCB0aGUgZGF0YSB0eXBlcyBmb3IgZWFjaCBjb2x1bW4gYW5kIHRoZSBmaXJzdCB2aWV3IHZhbHVlcyBpbiBlYWNoIGNvbHVtbi4gClRoZSBvdXRwdXQgYmVsb3cgaXMgc2Nyb2xsYWJsZSBzbyB5b3UgY2FuIHNlZSBldmVyeXRoaW5nIGZyb20gdGhlIGBnbGltcHNlKClgIGZ1bmN0aW9uLiAKCiMjIyMgey5zY3JvbGxhYmxlIH0KCmBgYHtyfQojIFNjcm9sbCB0aHJvdWdoIHRoZSBvdXRwdXQhCnBtICU+JQogIGRwbHlyOjpnbGltcHNlKCkKYGBgCgojIyMjCgpXZSBjYW4gc2VlIHRoYXQgdGhlcmUgYXJlIDg3NiBtb25pdG9ycyAocm93cykgYW5kIHRoYXQgd2UgaGF2ZSA1MCB0b3RhbCB2YXJpYWJsZXMgKGNvbHVtbnMpIC0gb25lIG9mIHdoaWNoIGlzIHRoZSBvdXRjb21lIHZhcmlhYmxlLiBJbiB0aGlzIGNhc2UsIHRoZSBvdXRjb21lIHZhcmlhYmxlIGlzIGNhbGxlZCBgdmFsdWVgLiAKCkFWT0NBRE86IFNob3VsZCB3ZSBsaW5rIHRvIGEgY29kZSBib29rIGhlcmU/IExpa2UgY291bGQgd2UgdXNlIHRoaXMgYXMgYSB0ZWFjaGluZyBvcHBvcnR1bml0eSB0byBkZW1vbnN0cmF0ZSB0aGUgcHVycG9zZSBvZiBhIGNvZGUgYm9vayBzbyBhIHN0dWRlbnQga25vd3MgaG93IHdlIGtub3cgdGhlIFBNIHZhbHVlcyBhcmUgYHZhbHVlYD8KCmF2b2NhZG8gcmVzcG9uc2U6IHRoZXJlIGlzbnQgYSBjb2RlIGJvb2sgcmVsYXRlZCB0byB0aGlzIGRhdGEsIFJvZ2VyIHdyYW5nbGVkIGl0IGZvciBtZSwgYnV0IHRoZSB2YXBpbmcgY2FzZSBzdHVkeSBhY3R1YWxseSB1c2VzIGNvZGUgYm9va3MgZm9yIHRoYXQgZGF0YS4gV291bGQgeW91IGxpa2UgdG8gbGluayB0byBhIGdlbmVyaWMgY29kZSBib29rIGFuZCBkZXNjcmliZSB0aGF0IHByb2Nlc3M/IEkgZGlkIGhvd2V2ZXIgYWRkIGluZm9ybWF0aW9uIGFib3V0IFJvZ2VyIGFuZCBob3cgaGUgcHJldmlvdXNseSBjb2xsZWN0ZWQgdGhlIGRhdGEuCgpOb3RpY2UgdGhhdCBzb21lIG9mIHRoZSB2YXJpYWJsZXMgdGhhdCB3ZSB3b3VsZCB0aGluayBvZiBhcyBmYWN0b3JzIChvciBjYXRlZ29yaWNhbCBkYXRhKSBhcmUgY3VycmVudGx5IG9mIGNsYXNzIGNoYXJhY3RlciBhcyBpbmRpY2F0ZWQgYnkgdGhlIGA8Y2hyPmAganVzdCB0byB0aGUgcmlnaHQgb2YgdGhlIGNvbHVtbiBuYW1lcy92YXJpYWJsZSBuYW1lcyBpbiB0aGUgYGdsaW1wc2UoKWAgb3V0cHV0LiBUaGlzIG1lYW5zIHRoYXQgdGhlIHZhcmlhYmxlIHZhbHVlcyBhcmUgY2hhcmFjdGVyIHN0cmluZ3MsIHN1Y2ggYXMgd29yZHMgb3IgcGhyYXNlcy4gCgpUaGUgb3RoZXIgdmFyaWFibGVzIGFyZSBvZiBjbGFzcyBgPGRibD5gLCB3aGljaCBzdGFuZHMgZm9yIGRvdWJsZSBwcmVjaXNpb24gd2hpY2ggaW5kaWNhdGVzIHRoYXQgdGhlIGFyZSBudW1lcmljIGFuZCB0aGF0IHRoZXkgaGF2ZSBkZWNpbWFsIHZhbHVlcy4gSW4gY29udHJhc3QsIG9uZSBjb3VsZCBoYXZlIGludGVnZXIgdmFsdWVzIHdoaWNoIHdvdWxkIG5vdCBhbGxvdyBmb3IgZGVjaW1hbCBudW1iZXJzLiBIZXJlIGlzIGEgW2xpbmtdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0RvdWJsZS1wcmVjaXNpb25fZmxvYXRpbmctcG9pbnRfZm9ybWF0KXt0YXJnZXQ9Il9ibGFuayJ9IGZvciBtb3JlIGluZm9ybWF0aW9uIG9uIGRvdWJsZSBwcmVjaXNpb24gbnVtZXJpYyB2YWx1ZXMuCgpBbm90aGVyIGNvbW1vbiBkYXRhIGNsYXNzIGlzIGZhY3RvciB3aGljaCBpcyBhYmJyZXZpYXRlZCBsaWtlIHRoaXM6IGA8ZmN0PmAuIEEgZmFjdG9yIGlzIHNvbWV0aGluZyB0aGF0IGhhcyB1bmlxdWUgbGV2ZWxzIGJ1dCB0aGVyZSBpcyBubyBhcHByZWNpYWJsZSBvcmRlciB0byB0aGUgbGV2ZWxzLiBGb3IgZXhhbXBsZSB3ZSBjYW4gaGF2ZSBhIG51bWVyaWMgdmFsdWUgdGhhdCBpcyBqdXN0IGFuIGlkIHRoYXQgd2Ugd2FudCB0byBiZSBpbnRlcnByZXRlZCBhcyBqdXN0IGEgdW5pcXVlIGxldmVsIGFuZCBub3QgYXMgdGhlIG51bWJlciB0aGF0IGl0IHdvdWxkIHR5cGljYWxseSBpbmRpY2F0ZS4gVGhpcyB3b3VsZCBiZSB1c2VmdWwgZm9yIHNldmVyYWwgb2Ygb3VyIHZhcmlhYmxlczoKCjEuIHRoZSBtb25pdG9yIElEIChgaWRgKQoyLiB0aGUgRmVkZXJhbCBJbmZvcm1hdGlvbiBQcm9jZXNzaW5nIFN0YW5kYXJkIG51bWJlciBmb3IgdGhlIGNvdW50eSB3aGVyZSB0aGUgbW9uaXRvciB3YXMgbG9jYXRlZCAoYGZpcHNgKQozLiB0aGUgemlwIGNvZGUgdGFidWxhdGlvbiBhcmVhIChgemN0YWApCgpOb25lIG9mIHRoZSB2YWx1ZXMgYWN0dWFsbHkgaGF2ZSBhbnkgcmVhbCBudW1lcmljIG1lYW5pbmcsIHNvIHdlIHdhbnQgdG8gbWFrZSBzdXJlIHRoYXQgUiBkb2VzIG5vdCBpbnRlcnByZXQgdGhlbSBhcyBpZiB0aGV5IGRvLiAKClNvIGxldCdzIGNvbnZlcnQgdGhlc2UgdmFyaWFibGVzIGludG8gZmFjdG9ycy4gCldlIGNhbiBkbyB0aGlzIHVzaW5nIHRoZSBgYWNyb3NzKClgIGZ1bmN0aW9uIG9mIHRoZSBgZHBseXJgIHBhY2thZ2UgYW5kIHRoZSBgYXMuZmFjdG9yKClgIGJhc2UgZnVuY3Rpb24uIApUaGUgYGFjcm9zcygpYCBmdW5jdGlvbiBoYXMgdHdvIG1haW4gYXJndW1lbnRzOiAoaSkgdGhlIGNvbHVtbnMgeW91IHdhbnQgdG8gb3BlcmF0ZSBvbiBhbmQgKGlpKSB0aGUgZnVuY3Rpb24gb3IgbGlzdCBvZiBmdW5jdGlvbnMgdG8gYXBwbHkgdG8gZWFjaCBjb2x1bW4uIAoKSW4gdGhpcyBjYXNlLCB3ZSBhcmUgYWxzbyB1c2luZyB0aGUgYG1hZ3JpdHRyYCBhc3NpZ25tZW50IHBpcGUgb3IgZG91YmxlIHBpcGUgdGhhdCBsb29rcyBsaWtlIHRoaXMgYCU8PiVgIG9mIHRoZSBgbWFncml0dHJgIHBhY2thZ2UuIApUaGlzIGFsbG93cyB1cyB1c2UgdGhlIGBwbWAgZGF0YSBhcyBpbnB1dCwgYnV0IGFsc28gcmVhc3NpZ25zIHRoZSBvdXRwdXQgdG8gdGhlIHNhbWUgZGF0YSBvYmplY3QgbmFtZS4KCiMjIyMgey5zY3JvbGxhYmxlIH0KCmBgYHtyfQojIFNjcm9sbCB0aHJvdWdoIHRoZSBvdXRwdXQhCnBtICU8PiUKICBtdXRhdGUoYWNyb3NzKGMoaWQsIGZpcHMsIHpjdGEpLCBhcy5mYWN0b3IpKSAKCmdsaW1wc2UocG0pCmBgYAoKIyMjIwoKR3JlYXQhIE5vdyB3ZSBjYW4gc2VlIHRoYXQgdGhlc2UgdmFyaWFibGVzIGFyZSBub3cgZmFjdG9ycyBhcyBpbmRpY2F0ZWQgYnkgYDxmY3Q+YCBhZnRlciB0aGUgdmFyaWFibGUgbmFtZS4KCiMjIyBTa2ltIHBhY2thZ2UKClRoZSBgc2tpbSgpYCBmdW5jdGlvbiBvZiB0aGUgYHNraW1yYCBwYWNrYWdlIGlzIGFsc28gcmVhbGx5IGhlbHBmdWwgZm9yIGdldHRpbmcgYSBnZW5lcmFsIHNlbnNlIG9mIHlvdXIgZGF0YS4KQnkgZGVzaWduLCBpdCBwcm92aWRlcyBzdW1tYXJ5IHN0YXRpc3RpY3MgYWJvdXQgdmFyaWFibGVzIGluIHRoZSBkYXRhIHNldC4gCgoKIyMjIyB7LnNjcm9sbGFibGUgfQoKYGBge3J9CiMgU2Nyb2xsIHRocm91Z2ggdGhlIG91dHB1dCEKc2tpbShwbSkKYGBgCgojIyMjCgpOb3RpY2UgaG93IHRoZXJlIGlzIGEgY29sdW1uIGNhbGxlZCBgbl9taXNzaW5nYCBhYm91dCB0aGUgbnVtYmVyIG9mIHZhbHVlcyB0aGF0IGFyZSBtaXNzaW5nLiAKClRoaXMgaXMgYWxzbyBpbmRpY2F0ZWQgYnkgdGhlIGBjb21wbGV0ZV9yYXRlYCB2YXJpYWJsZSAob3IgbWlzc2luZy9udW1iZXIgb2Ygb2JzZXJ2YXRpb25zKS4gCgpJbiBvdXIgZGF0YSBzZXQsIGl0IGxvb2tzIGxpa2Ugb3VyIGRhdGEgZG8gbm90IGNvbnRhaW4gYW55IG1pc3NpbmcgZGF0YS4gCgpBbHNvIG5vdGljZSBob3cgdGhlIGZ1bmN0aW9uIHByb3ZpZGVzIHNlcGFyYXRlIHRhYmxlcyBvZiBzdW1tYXJ5IHN0YXRpc3RpY3MgZm9yIGVhY2ggZGF0YSB0eXBlOiBjaGFyYWN0ZXIsIGZhY3RvciBhbmQgbnVtZXJpYy4gCgpOZXh0LCB0aGUgYG5fdW5xaXVlYCBjb2x1bW4gc2hvd3MgdXMgdGhlIG51bWJlciBvZiB1bmlxdWUgdmFsdWVzIGZvciBlYWNoIG9mIG91ciBjb2x1bW5zLiAKV2UgY2FuIHNlZSB0aGF0IHRoZXJlIGFyZSA0OSBzdGF0ZXMgcmVwcmVzZW50ZWQgaW4gdGhlIGRhdGEuCgpXZSBjYW4gc2VlIHRoYXQgZm9yIG1hbnkgdmFyaWFibGVzIHRoZXJlIGFyZSBtYW55IGxvdyB2YWx1ZXMgYXMgdGhlIGRpc3RyaWJ1dGlvbiBzaG93cyB0d28gcGVha3MsIG9uZSBuZWFyIHplcm8gYW5kIGFub3RoZXIgd2l0aCBhIGhpZ2hlciB2YWx1ZS4gCgpUaGlzIGlzIHRydWUgZm9yIHRoZSBgaW1wYCB2YXJpYWJsZXMgKG1lYXN1cmVzIG9mIGRldmVsb3BtZW50KSwgdGhlIGBuZWlgIHZhcmlhYmxlcyAobWVhc3VyZXMgb2YgZW1pc3Npb24gc291cmNlcykgYW5kIHRoZSByb2FkIGRlbnNpdHkgdmFyaWFibGVzLiAKCldlIGNhbiBhbHNvIHNlZSB0aGF0IHRoZSByYW5nZSBvZiBzb21lIG9mIHRoZSB2YXJpYWJsZXMgaXMgdmVyeSBsYXJnZSwgaW4gcGFydGljdWxhciB0aGUgYXJlYSBhbmQgcG9wdWxhdGlvbiByZWxhdGVkIHZhcmlhYmxlcy4KCgpMZXQncyB0YWtlIGEgbG9vayB0byBzZWUgd2hpY2ggc3RhdGVzIGFyZSBpbmNsdWRlZCB1c2luZyB0aGUgYGRpc3RpbmN0KClgIGZ1bmN0aW9uIG9mIHRoZSBgZHBseXJgIHBhY2thZ2U6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfSAKcG0gJT4lIAogIGRpc3RpbmN0KHN0YXRlKSAKYGBgCgoKU2Nyb2xsIHRocm91Z2ggdGhlIG91dHB1dDoKCiMjIyMgey5zY3JvbGxhYmxlIH0KYGBge3IsIGVjaG8gPSBGQUxTRX0KIyBTY3JvbGwgdGhyb3VnaCB0aGUgb3V0cHV0IQpwbSAlPiUgCiAgZGlzdGluY3Qoc3RhdGUpICU+JQojIHRoaXMgYWxsb3dzIHVzIHRvIHNob3cgdGhlIGZ1bGwgb3V0cHV0IGluIHRoZSByZW5kZXJlZCBybWFya2Rvd24KIHByaW50KG4gPSAxZTMpCmBgYAojIyMjCgpJdCBsb29rcyBsaWtlICJEaXN0cmljdCBvZiBDb2x1bWJpYSIgaXMgYmVpbmcgaW5jbHVkZWQgYXMgYSBzdGF0ZS4gCldlIGNhbiBzZWUgdGhhdCBBbGFza2EgYW5kIEhhd2FpaSBhcmUgbm90IGluY2x1ZGVkIGluIHRoZSBkYXRhLgoKCiMjIEV2YWx1YXRlIGNvcnJlbGF0aW9uCgpJbiBwcmVkaWN0aW9uIGFuYWx5c2VzLCBpdCBpcyBhbHNvIHVzZWZ1bCB0byBldmFsdWF0ZSBpZiBhbnkgb2YgdGhlIHZhcmlhYmxlcyBhcmUgY29ycmVsYXRlZC4gV2h5IHNob3VsZCB3ZSBjYXJlIGFib3V0IHRoaXM/CgpJZiB3ZSBhcmUgdXNpbmcgYSBsaW5lYXIgcmVncmVzc2lvbiB0byBtb2RlbCBvdXIgZGF0YSB0aGVuIHdlIG1pZ2h0IHJ1biBpbnRvIGEgcHJvYmxlbSBjYWxsZWQgbXVsdGljb2xpbmVhcml0eSB3aGljaCBjYW4gbGVhZCB1cyB0byBtaXNpbnRlcnByZXQgd2hhdCBpcyByZWFsbHkgcHJlZGljdGl2ZSBvZiBvdXIgb3V0Y29tZSB2YXJpYWJsZS4gVGhpcyBwaGVub21lbm9uIG9jY3VycyB3aGVuIHRoZSBwcmVkaWN0b3IgdmFyaWFibGVzIGFjdHVhbGx5IHByZWRpY3Qgb25lIGFub3RoZXIuIFNlZSBbdGhpcyBjYXNlIHN0dWR5XShodHRwczovL29wZW5jYXNlc3R1ZGllcy5naXRodWIuaW8vb2NzLWJwLVJUQy1hbmFseXNpcy8pIGZvciBhIGRlZXBlciBleHBsYW5hdGlvbiBhYm91dCB0aGlzLiAKCkFub3RoZXIgcmVhc29uIHdlIHNob3VsZCBsb29rIG91dCBmb3IgY29ycmVsYXRpb24gaXMgdGhhdCB3ZSBkb24ndCB3YW50IHRvIGluY2x1ZGUgcmVkdW5kYW50IHZhcmlhYmxlcy4gVGhpcyBjYW4gYWRkIHVubmVjZXNzYXJ5IG5vaXNlIHRvIG91ciBhbGdvcml0aG0gY2F1c2luZyBhIHJlZHVjdGlvbiBpbiBwcmVkaWN0aW9uIGFjY3VyYWN5IGFuZCBpdCBjYW4gY2F1c2Ugb3VyIGFsZ29yaXRobSB0byBiZSB1bm5lY2Vzc2FyaWx5IHNsb3dlci4gRmluYWxseSwgaXQgY2FuIGFsc28gbWFrZSBpdCBkaWZmaWN1bHQgdG8gaW50ZXJwcmV0IHdoYXQgdmFyaWFibGVzIGFyZSBhY3R1YWxseSBwcmVkaWN0aXZlLgoKSW50dWl0aXZlbHkgd2UgY2FuIGV4cGVjdCBzb21lIG9mIG91ciB2YXJpYWJsZXMgdG8gYmUgY29ycmVsYXRlZC4KCkxldCdzIGZpcnN0IHRha2UgYSBsb29rIGF0IGFsbCBvZiBvdXIgbnVtZXJpYyB2YXJpYWJsZXMgd2l0aCB0aGVgY29ycnBsb3RgIHBhY2thZ2U6ClRoZSBgY29ycnBsb3RgIHBhY2thZ2UgaXMgYW5vdGhlciBvcHRpb24gdG8gbG9vayBhdCBjb3JyZWxhdGlvbiBhbW9uZyBwb3NzaWJsZSBwcmVkaWN0b3JzLCBhbmQgcGFydGljdWxhcmx5IHVzZWZ1bCBpZiB3ZSBoYXZlIG1hbnkgcHJlZGljdG9ycy4gCgpGaXJzdCwgd2UgY2FsY3VsYXRlIHRoZSBQZWFyc29uIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50cyBiZXR3ZWVuIGFsbCBmZWF0dXJlcyBwYWlyd2lzZSB1c2luZyB0aGUgYGNvcigpYCBmdW5jdGlvbiBvZiB0aGUgYHN0YXRzYCBwYWNrYWdlICh3aGljaCBpcyBsb2FkZWQgYXV0b21hdGljYWxseSkuIFRoZW4gd2UgdXNlIHRoZSBgY29ycnBsb3Q6OmNvcnJwbG90KClgIGZ1bmN0aW9uLiAKCmBgYHtyfQpQTV9jb3IgPC0gY29yKHBtICU+JSBkcGx5cjo6c2VsZWN0X2lmKGlzLm51bWVyaWMpKQpjb3JycGxvdDo6Y29ycnBsb3QoUE1fY29yLCB0bC5jZXggPSAwLjUpCmBgYApUaGUgYHRsLmNleCA9IDAuNWAgYXJndW1lbnQgY29udHJvbHMgdGhlIHNpemUgb2YgdGhlIHRleHQgbGFiZWwuIAoKV2UgY2FuIGFsc28gcGxvdCB0aGUgYWJzb2x1dGUgdmFsdWUgb2YgdGhlIFBlYXJzb24gY29ycmVsYXRpb24gY29lZmZpY2llbnRzIHVzaW5nIHRoZSBgYWJzKClgIGZ1bmN0aW9uIGZyb20gYmFzZSBSIGFuZCBjaGFuZ2UgdGhlIG9yZGVyIG9mIHRoZSBjb2x1bW5zLiAgCmBgYHtyfQpjb3JycGxvdChhYnMoUE1fY29yKSwgb3JkZXIgPSAiaGNsdXN0IiwgdGwuY2V4ID0gMC41LCBjbC5saW0gPSBjKDAsIDEpKQoKYGBgCgpUaGVyZSBhcmUgc2V2ZXJhbCBvcHRpb25zIGZvciBvcmRlcmluZyB0aGUgdmFyaWFibGVzLiBTZWUgW2hlcmVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9jb3JycGxvdC92aWduZXR0ZXMvY29ycnBsb3QtaW50cm8uaHRtbCkgZm9yIG1vcmUgb3B0aW9ucy4gSGVyZSB3ZSB3aWxsIHVzZSB0aGUgImhjbHVzdCIgb3B0aW9uIGZvciBvcmRlcmluZyBieSBbaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmddKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0hpZXJhcmNoaWNhbF9jbHVzdGVyaW5nKSAtIHdoaWNoIHdpbGwgb3JkZXIgdGhlIHZhcmlhYmxlcyBieSBob3cgc2ltaWxhciB0aGV5IGFyZSB0byBvbmUgYW5vdGhlci4KClRoZSBgY2wubGltID0gYygwLCAxKWAgYXJndW1lbnQgbGltaXRzIHRoZSBjb2xvciBsYWJlbCB0byBiZSBiZXR3ZWVuIDAgYW5kIDEuIAoKCldlIGNhbiBzZWUgdGhhdCB0aGUgZGV2ZWxvcG1lbnQgdmFyaWFibGVzIChgaW1wYCkgdmFyaWFibGVzIGFyZSBjb3JyZWxhdGVkIHdpdGggZWFjaCBvdGhlciBhcyB3ZSBtaWdodCBleHBlY3QuIApXZSBhbHNvIHNlZSB0aGF0IHRoZSByb2FkIGRlbnNpdHkgdmFyaWFibGVzIHNlZW0gdG8gYmUgY29ycmVsYXRlZCB3aXRoIGVhY2ggb3RoZXIsIGFuZCB0aGUgZW1pc3Npb24gdmFyaWFibGVzIHNlZW0gdG8gYmUgY29ycmVsYXRlZCB3aXRoIGVhY2ggb3RoZXIuIAoKCkFsc28gbm90aWNlIHRoYXQgbm9uZSBvZiB0aGUgcHJlZGljdG9ycyBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQgd2l0aCBvdXIgb3V0Y29tZSB2YXJpYWJsZSAoYHZhbHVlYCkuCgpXZSBjYW4gdGFrZSBhbHNvIHRha2UgYSBjbG9zZXIgbG9vayAgdXNpbmcgdGhlIGBnZ2NvcnIoKWAgZnVuY3Rpb24gYW5kIHRoZSBgZ2dwYWlycygpYCBmdW5jdGlvbiBvZiB0aGUgYEdHYWxseWAgcGFja2FnZS4gCgpUbyBzZWxlY3Qgb3VyIHZhcmlhYmxlcyBvZiBpbnRlcmVzdCB3ZSBjYW4gdXNlIHRoZSBgc2VsZWN0KClgIGZ1bmN0aW9uIHdpdGggdGhlIGBjb250YWlucygpYCBmdW5jdGlvbiBvZiB0aGUgYHRpZHlyYCBwYWNrYWdlLiAKCkZpcnN0IGxldCdzIGxvb2sgYXQgdGhlIGBpbXBgL2RldmVsb3BtZW50IHZhcmlhYmxlcy4gCldlIGNhbiBjaGFuZ2UgdGhlIGRlZmF1bHQgY29sb3IgcGFsZXR0ZSAoYHBhbGV0dGUgPSAiUmRCdSJgKSBhbmQgYWRkIG9uIApjb3JyZWxhdGlvbiBjb2VmZmljaWVudHMgdG8gdGhlIHBsb3QgKGBsYWJlbCA9IFRSVUVgKS4KCmBgYHtyLCBvdXQud2lkdGggPSAiNDAwcHgifQpzZWxlY3QocG0sIGNvbnRhaW5zKCJpbXAiKSkgJT4lCiAgZ2djb3JyKHBhbGV0dGUgPSAiUmRCdSIsIGxhYmVsID0gVFJVRSkKCnNlbGVjdChwbSwgY29udGFpbnMoImltcCIpKSAlPiUKICBnZ3BhaXJzKCkKYGBgCgoKCkluZGVlZCwgd2UgY2FuIHNlZSB0aGF0IGBpbXBfYTEwMDBgIGFuZCBgaW1wX2E1MDBgIGFyZSBoaWdobHkgY29ycmVsYXRlZCwgYXMgd2VsbCBhcyBgaW1wX2ExMDAwMGAsIGBpbXBfYTE1MDAwYC4KCk5leHQsIGxldCdzIHRha2UgYSBsb29rIGF0IHRoZSByb2FkIGRlbnNpdHkgZGF0YToKCmBgYHtyLCBmaWcud2VpZ2h0PTEyfQpzZWxlY3QocG0sIGNvbnRhaW5zKCJwcmkiKSkgJT4lCiAgZ2djb3JyKHBhbGV0dGUgPSAiUmRCdSIsIGhqdXN0ID0gLjg1LCBzaXplID0gMywKICAgICAgIGxheW91dC5leHA9MiwgbGFiZWwgPSBUUlVFKQpgYGAKCldlIGNhbiBzZWUgdGhhdCBtYW55IG9mIHRoZSByb2FkIGRlbnNpdHkgdmFyaWFibGVzIGFyZSBoaWdobHkgY29ycmVsYXRlZCB3aXRoIG9uZSBhbm90aGVyLCB3aGlsZSBvdGhlcnMgYXJlIGxlc3Mgc28uCgpGaW5hbGx5IGxldCdzIGxvb2sgYXQgdGhlIGVtaXNzaW9uIHZhcmlhYmxlcy4KCmBgYHtyfQpzZWxlY3QocG0sIGNvbnRhaW5zKCJuZWkiKSkgJT4lCiAgZ2djb3JyKHBhbGV0dGUgPSAiUmRCdSIsIGhqdXN0ID0gLjg1LCBzaXplID0gMywKICAgICAgIGxheW91dC5leHA9MiwgbGFiZWwgPSBUUlVFKQoKc2VsZWN0KHBtLCBjb250YWlucygibmVpIikpICU+JQogIGdncGFpcnMoKQpgYGAKCldlIHdvdWxkIGFsc28gZXhwZWN0IHRoZSBwb3B1bGF0aW9uIGRlbnNpdHkgZGF0YSBtaWdodCBjb3JyZWxhdGUgd2l0aCBzb21lIG9mIHRoZXNlIHZhcmlhYmxlcy4gCkxldCdzIHRha2UgYSBsb29rLgoKYGBge3J9CnBtICU+JQpzZWxlY3QobG9nX25laV8yMDA4X3BtMjVfc3VtXzEwMDAwLCBwb3BkZW5zX2NvdW50eSwgCiAgICAgICBsb2dfcHJpX2xlbmd0aF8xMDAwMCwgaW1wX2ExMDAwMCkgJT4lCiAgZ2djb3JyKHBhbGV0dGUgPSAiUmRCdSIsICBoanVzdCA9IC44NSwgc2l6ZSA9IDMsCiAgICAgICBsYXlvdXQuZXhwPTIsIGxhYmVsID0gVFJVRSkKCnBtICU+JQpzZWxlY3QobG9nX25laV8yMDA4X3BtMjVfc3VtXzEwMDAwLCBwb3BkZW5zX2NvdW50eSwgCiAgICAgICBsb2dfcHJpX2xlbmd0aF8xMDAwMCwgaW1wX2ExMDAwMCwgY291bnR5X3BvcCkgJT4lCiAgZ2dwYWlycygpCmBgYAoKCkludGVyZXN0aW5nLCBzbyB0aGVzZSB2YXJpYWJsZXMgZG9uJ3QgYXBwZWFyIHRvIGJlIGhpZ2hseSBjb3JyZWxhdGVkLCB0aGVyZWZvcmUgd2UgbWlnaHQgbmVlZCB2YXJpYWJsZXMgZnJvbSBlYWNoIG9mIHRoZSBjYXRlZ29yaWVzIHRvIHByZWRpY3Qgb3VyIG1vbml0b3IgUE1+Mi41fiBwb2xsdXRpb24gdmFsdWVzLgoKQmVjYXVzZSBzb21lIHZhcmlhYmxlcyBpbiBvdXIgZGF0YSBoYXZlIGV4dHJlbWUgdmFsdWVzLCBpdCBtaWdodCBiZSBnb29kIHRvIHRha2UgYSBsb2cgdHJhbnNmb3JtYXRpb24uIFRoaXMgY2FuIGFmZmVjdCBvdXIgZXN0aW1hdGVzIG9mIGNvcnJlbGF0aW9uLiAKYGBge3J9CnBtICU+JQogIG11dGF0ZShsb2dfcG9wZGVuc19jb3VudHk9IGxvZyhwb3BkZW5zX2NvdW50eSkpICU+JQpzZWxlY3QobG9nX25laV8yMDA4X3BtMjVfc3VtXzEwMDAwLCBsb2dfcG9wZGVuc19jb3VudHksIAogICAgICAgbG9nX3ByaV9sZW5ndGhfMTAwMDAsIGltcF9hMTAwMDApICU+JQogIGdnY29ycihwYWxldHRlID0gIlJkQnUiLCAgaGp1c3QgPSAuODUsIHNpemUgPSAzLAogICAgICAgbGF5b3V0LmV4cD0yLCBsYWJlbCA9IFRSVUUpCgpwbSAlPiUKICBtdXRhdGUobG9nX3BvcGRlbnNfY291bnR5PSBsb2cocG9wZGVuc19jb3VudHkpKSAlPiUKICBtdXRhdGUobG9nX3BvcF9jb3VudHkgPSBsb2coY291bnR5X3BvcCkpICU+JQpzZWxlY3QobG9nX25laV8yMDA4X3BtMjVfc3VtXzEwMDAwLCBsb2dfcG9wZGVuc19jb3VudHksIAogICAgICAgbG9nX3ByaV9sZW5ndGhfMTAwMDAsIGltcF9hMTAwMDAsIGxvZ19wb3BfY291bnR5KSAlPiUKICBnZ3BhaXJzKCkKYGBgCgpJbmRlZWQgdGhpcyBpbmNyZWFzZWQgdGhlIGNvcnJlbGF0aW9uLCBidXQgdmFyaWFibGVzIGZyb20gZWFjaCBvZiB0aGVzZSBjYXRlZ29yaWVzIG1heSBzdGlsbCBwcm92ZSB0byBiZSB1c2VmdWwgZm9yIHByZWRpY3Rpb24uCgpOb3cgdGhhdCB3ZSBoYXZlIGEgc2Vuc2Ugb2Ygd2hhdCBvdXIgZGF0YSBhcmUsIHdlIGNhbiBnZXQgc3RhcnRlZCB3aXRoIGJ1aWxkaW5nIGEgbWFjaGluZSBsZWFybmluZyBtb2RlbCB0byBwcmVkaWN0IGFpciBwb2xsdXRpb24uIAoKIyAqKldoYXQgaXMgbWFjaGluZSBsZWFybmluZz8qKiAgeyN3aGF0aXNtbH0KKioqCgpZb3UgbWF5IGhhdmUgbGVhcm5lZCBhYm91dCB0aGUgY2VudHJhbCBkb2dtYSBvZiBzdGF0aXN0aWNzIHRoYXQgeW91IHNhbXBsZSBmcm9tIGEgcG9wdWxhdGlvbi4KCiFbXShpbWcvY2RpMS5wbmcpCgpUaGVuIHlvdSB1c2UgdGhlIHNhbXBsZSB0byB0cnkgdG8gZ3Vlc3Mgd2hhdCBpcyBoYXBwZW5pbmcgaW4gdGhlIHBvcHVsYXRpb24uCgohW10oaW1nL2NkaTIucG5nKQoKRm9yIHByZWRpY3Rpb24gd2UgaGF2ZSBhIHNpbWlsYXIgc2FtcGxpbmcgcHJvYmxlbQoKIVtdKGltZy9jZHAxLnBuZykKCkJ1dCBub3cgd2UgYXJlIHRyeWluZyB0byBidWlsZCBhIHJ1bGUgdGhhdCBjYW4gYmUgdXNlZCB0byBwcmVkaWN0IGEgc2luZ2xlIG9ic2VydmF0aW9uJ3MgdmFsdWUgb2Ygc29tZSBjaGFyYWN0ZXJpc3RpYyB1c2luZyBjaGFyYWN0ZXJpc3RpY3Mgb2YgdGhlIG90aGVyIG9ic2VydmF0aW9ucy4gCgohW10oaW1nL2NkcDIucG5nKQoKTGV0J3MgbWFrZSB0aGlzIG1vcmUgY29uY3JldGUuCgpJZiB5b3UgcmVjYWxsIGZyb20gdGhlIFtXaGF0IGFyZSB0aGUgZGF0YT9dKCN3aGF0YXJldGhlZGF0YSkgc2VjdGlvbiBhYm92ZSwgd2hlbiB3ZSBhcmUgdXNpbmcgbWFjaGluZSBsZWFybmluZyBmb3IgcHJlZGljdGlvbiwgb3VyIGRhdGEgY29uc2lzdHMgb2Y6IAoKMS4gQW4gKipjb250aW51b3VzKiogb3V0Y29tZSB2YXJpYWJsZSB0aGF0IHdlIHdhbnQgdG8gcHJlZGljdCAKMi4gQSBzZXQgb2YgZmVhdHVyZShzKSAob3IgcHJlZGljdG9yIHZhcmlhYmxlcykgdGhhdCB3ZSB1c2UgdG8gcHJlZGljdCB0aGUgb3V0Y29tZSB2YXJpYWJsZQoKV2Ugd2lsbCB1c2UgJFkkIHRvIGRlbm90ZSB0aGUgb3V0Y29tZSB2YXJpYWJsZSBhbmQgJFggPSAoWF8xLCBcZG90cywgWF9wKSQgdG8gZGVub3RlICRwJCBkaWZmZXJlbnQgZmVhdHVyZXMgKG9yIHByZWRpY3RvciB2YXJpYWJsZXMpLiAKQmVjYXVzZSBvdXIgb3V0Y29tZSB2YXJpYWJsZSBpcyAqKmNvbnRpbnVvdXMqKiAoYXMgb3Bwb3NlZCB0byBjYXRlZ29yaWNhbCksIHdlIGFyZSBpbnRlcmVzdGVkIGluIGEgcGFydGljdWxhciB0eXBlIG9mIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtLiAKCk91ciBnb2FsIGlzIHRvIGJ1aWxkIGEgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG0gdGhhdCB1c2VzIHRoZSBmZWF0dXJlcyAkWCQgYXMgaW5wdXQgYW5kIHByZWRpY3RzIGFuIG91dGNvbWUgdmFyaWFibGUgKG9yIGFpciBwb2xsdXRpb24gbGV2ZWxzKSBpbiB0aGUgc2l0dWF0aW9uIHdoZXJlIHdlIGRvIG5vdCBrbm93IHRoZSBvdXRjb21lIHZhcmlhYmxlLiAKClRoZSB3YXkgd2UgZG8gdGhpcyBpcyB0byB1c2UgZGF0YSB3aGVyZSB3ZSBoYXZlIGJvdGggdGhlIGZlYXR1cmVzICQoWF8xPXhfMSwgXGRvdHMgWF9wPXhfcCkkIGFuZCB0aGUgYWN0dWFsIG91dGNvbWUgJFkkIGRhdGEgdG8gX3RyYWluXyBhIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtIHRvIHByZWRpY3QgdGhlIG91dGNvbWUsIHdoaWNoIHdlIGNhbGwgJFxoYXR7WX0kLiAgCgpXaGVuIHdlIHNheSB0cmFpbiBhIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtIHdlIG1lYW4gdGhhdCB3ZSBlc3RpbWF0ZSBhIGZ1bmN0aW9uICRmJCB0aGF0IHVzZXMgdGhlIHByZWRpY3RvciB2YXJpYWJsZXMgJFgkIGFzIGlucHV0IG9yICRcaGF0e1l9ID0gZihYKSQuIAoKIyMgTUwgYXMgYW4gb3B0aW1pemF0aW9uIHByb2JsZW0KCklmIHdlIGFyZSBkb2luZyBhIGdvb2Qgam9iLCB0aGVuIG91ciBwcmVkaWN0ZWQgb3V0Y29tZSAkXGhhdHtZfSQgc2hvdWxkIGNsb3NlbHkgbWF0Y2ggb3VyIGFjdHVhbCBvdXRjb21lICRZJCB0aGF0IHdlIG9ic2VydmVkLiAKCkluIHRoaXMgd2F5LCB3ZSBjYW4gdGhpbmsgb2YgbWFjaGluZSBsZWFybmluZyAoTUwpIGFzIGFuIG9wdGltaXphdGlvbiBwcm9ibGVtIHRoYXQgdHJpZXMgdG8gbWluaW1pemUgdGhlIGRpc3RhbmNlIGJldHdlZW4gJFxoYXR7WX0gPSBmKFgpJCBhbmQgJFkkLiAKCiQkZChZIC0gZihYKSkkJApUaGUgY2hvaWNlIG9mIGRpc3RhbmNlIG1ldHJpYyAkZChcY2RvdCkkIGNhbiBiZSB0aGUgbWVhbiBvZiB0aGUgYWJzb2x1dGUgb3Igc3F1YXJlZCBkaWZmZXJlbmNlIG9yIHNvbWV0aGluZyBtb3JlIGNvbXBsaWNhdGVkLiAKCk11Y2ggb2YgdGhlIGZpZWxkcyBvZiBzdGF0aXN0aWNzIGFuZCBjb21wdXRlciBzY2llbmNlIGFyZSBmb2N1c2VkIG9uIGRlZmluaW5nICRmJCBhbmQgJGQkLgoKIyMgVGhlIHBhcnRzIG9mIGFuIE1MIHByb2JsZW0KClRvIHNldCB1cCBhIG1hY2hpbmUgbGVhcm5pbmcgKE1MKSBwcm9ibGVtLCB3ZSBuZWVkIGEgZmV3IGNvbXBvbmVudHMuClRvIHNvbHZlIGEgKHN0YW5kYXJkKSBtYWNoaW5lIGxlYXJuaW5nIHByb2JsZW0geW91IG5lZWQ6IAoKMS4gQSBkYXRhIHNldCB0byB0cmFpbiBmcm9tLiAKMi4gQW4gYWxnb3JpdGhtIG9yIHNldCBvZiBhbGdvcml0aG1zIHlvdSBjYW4gdXNlIHRvIHRyeSB2YWx1ZXMgb2YgJGYkCjMuIEEgZGlzdGFuY2UgbWV0cmljICRkJCBmb3IgbWVhc3VyaW5nIGhvdyBjbG9zZSAkWSQgaXMgdG8gJFxoYXR7WX0kCjQuIEEgZGVmaW5pdGlvbiBvZiB3aGF0IGEgImdvb2QiIGRpc3RhbmNlIGlzCgpXaGlsZSBlYWNoIG9mIHRoZXNlIGNvbXBvbmVudHMgaXMgYSBfdGVjaG5pY2FsXyBwcm9ibGVtLCB0aGVyZSBoYXMgYmVlbiBhIHRvbiBvZiB3b3JrIGFkZHJlc3NpbmcgdGhvc2UgdGVjaG5pY2FsIGRldGFpbHMuIFRoZSBtb3N0IHByZXNzaW5nIG9wZW4gaXNzdWUgaW4gbWFjaGluZSBsZWFybmluZyBpcyByZWFsaXppbmcgdGhhdCB0aG91Z2ggdGhlc2UgYXJlIF90ZWNobmljYWxfIHN0ZXBzIHRoZXkgYXJlIG5vdCBfb2JqZWN0aXZlXyBzdGVwcy4gSW4gb3RoZXIgd29yZHMsIGhvdyB5b3UgY2hvb3NlIHRoZSBkYXRhLCBhbGdvcml0aG0sIG1ldHJpYywgYW5kIGRlZmluaXRpb24gb2YgImdvb2QiIHNheXMgd2hhdCB5b3UgdmFsdWUgYW5kIGNhbiBkcmFtYXRpY2FsbHkgY2hhbmdlIHRoZSByZXN1bHRzLiBBIGNvdXBsZSBvZiBjYXNlcyB3aGVyZSB0aGlzIHdhcyBhIGJpZyBkZWFsIGFyZTogCgoxLiBbTWFjaGluZSBsZWFybmluZyBmb3IgcmVjaWRpdmlzbV0oaHR0cHM6Ly93d3cucHJvcHVibGljYS5vcmcvYXJ0aWNsZS9tYWNoaW5lLWJpYXMtcmlzay1hc3Nlc3NtZW50cy1pbi1jcmltaW5hbC1zZW50ZW5jaW5nKSAtIHBlb3BsZSBidWlsdCBNTCBtb2RlbHMgdG8gcHJlZGljdCB3aG8gd291bGQgcmUtY29tbWl0IGEgY3JpbWUuIEJ1dCB0aGVzZSBwcmVkaWN0aW9ucyB3ZXJlIGJhc2VkIG9uIGhpc3RvcmljYWxseSBiaWFzZWQgZGF0YSB3aGljaCBsZWQgdG8gYmlhc2VkIHByZWRpY3Rpb25zIGFib3V0IHdobyB3b3VsZCBjb21taXQgbmV3IGNyaW1lcy4gCjIuIFtEZWNpZGluZyBob3cgc2VsZiBkcml2aW5nIGNhcnMgc2hvdWxkIGFjdF0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9kNDE1ODYtMDE4LTA3MTM1LTApIC0gc2VsZiBkcml2aW5nIGNhcnMgd2lsbCBoYXZlIHRvIG1ha2UgZGVjaXNpb25zIGFib3V0IGhvdyB0byBkcml2ZSwgd2hvIHRoZXkgbWlnaHQgaW5qdXJlLCBhbmQgaG93IHRvIGF2b2lkIGFjY2lkZW50cy4gRGVwZW5kaW5nIG9uIG91ciBjaG9pY2VzIGZvciAkZiQgYW5kICRkJCB0aGVzZSBtaWdodCBsZWFkIHRvIHdpbGRseSBkaWZmZXJlbnQga2luZHMgb2Ygc2VsZiBkcml2aW5nIGNhcnMuIFRyeSBvdXQgdGhlIFttb3JhbG1hY2hpbmVdKGh0dHA6Ly9tb3JhbG1hY2hpbmUubWl0LmVkdS8pIHRvIHNlZSBob3cgdGhpcyBsb29rcyBpbiBwcmFjdGljZS4gCgpOb3cgdGhhdCB3ZSBrbm93IGEgYml0IG1vcmUgYWJvdXQgbWFjaGluZSBsZWFybmluZywgbGV0J3MgYnVpbGQgYSBtb2RlbCB0byBwcmVkaWN0IGFpciBwb2xsdXRpb24gbGV2ZWxzIHVzaW5nIHRoZSBgdGlkeW1vZGVsc2AgZnJhbWV3b3JrLiAKCiMgKipNYWNoaW5lIGxlYXJuaW5nIHdpdGggYHRpZHltb2RlbHNgKioKKioqClRoZSBnb2FsIGlzIHRvIGJ1aWxkIGEgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG0gdXNlcyB0aGUgZmVhdHVyZXMgYXMgaW5wdXQgYW5kIHByZWRpY3RzIGEgb3V0Y29tZSB2YXJpYWJsZSAob3IgYWlyIHBvbGx1dGlvbiBsZXZlbHMpIGluIHRoZSBzaXR1YXRpb24gd2hlcmUgd2UgZG8gbm90IGtub3cgdGhlIG91dGNvbWUgdmFyaWFibGUuIAoKVGhlIHdheSB3ZSBkbyB0aGlzIGlzIHRvIHVzZSBkYXRhIHdoZXJlIHdlIGhhdmUgYm90aCB0aGUgaW5wdXQgYW5kIG91dHB1dCBkYXRhIHRvIF90cmFpbl8gYSBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobS4gCgpUbyB0cmFpbiBhIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtLCB3ZSB3aWxsIHVzZSB0aGUgYHRpZHltb2RlbHNgIHBhY2thZ2UgZWNvc3lzdGVtLiAKCiMjIE92ZXJ2aWV3IAoKIyMjIFRoZSB0aWR5bW9kZWxzIGVjb3N5c3RlbQoKVG8gcGVyZm9ybSBvdXIgYW5hbHlzaXMgd2Ugd2lsbCBiZSB1c2luZyB0aGUgYHRpZHltb2RlbHNgIHN1aXRlIG9mIHBhY2thZ2VzLiAKWW91IG1heSBiZSBmYW1pbGlhciB3aXRoIHRoZSBvbGRlciBwYWNrYWdlcyBgY2FyZXRgIG9yIGBtbHJgIHdoaWNoIGFyZSBhbHNvIGZvciBtYWNoaW5lIGxlYXJuaW5nIGFuZCBtb2RlbGluZyBidXQgYXJlIG5vdCBhIHBhcnQgb2YgdGhlIGB0aWR5dmVyc2VgLiAKW01heCBLdWhuXShodHRwczovL3Jlc291cmNlcy5yc3R1ZGlvLmNvbS9hdXRob3JzL21heC1rdWhuKXt0YXJnZXQ9Il9ibGFuayJ9IGRlc2NyaWJlcyBgdGlkeW1vZGVsc2AgbGlrZSB0aGlzOgoKPiAiT3RoZXIgcGFja2FnZXMsIHN1Y2ggYXMgY2FyZXQgYW5kIG1sciwgaGVscCB0byBzb2x2ZSB0aGUgUiBtb2RlbCBBUEkgaXNzdWUuIFRoZXNlIHBhY2thZ2VzIGRvIGEgbG90IG9mIG90aGVyIHRoaW5ncyB0b286IHByZS1wcm9jZXNzaW5nLCBtb2RlbCB0dW5pbmcsIHJlc2FtcGxpbmcsIGZlYXR1cmUgc2VsZWN0aW9uLCBlbnNlbWJsaW5nLCBhbmQgc28gb24uIEluIHRoZSB0aWR5dmVyc2UsIHdlIHN0cml2ZSB0byBtYWtlIG91ciBwYWNrYWdlcyBtb2R1bGFyIGFuZCBwYXJzbmlwIGlzIGRlc2lnbmVkIG9ubHkgdG8gc29sdmUgdGhlIGludGVyZmFjZSBpc3N1ZS4gSXQgaXMgbm90IGRlc2lnbmVkIHRvIGJlIGEgZHJvcC1pbiByZXBsYWNlbWVudCBmb3IgY2FyZXQuClRoZSB0aWR5bW9kZWxzIHBhY2thZ2UgY29sbGVjdGlvbiwgd2hpY2ggaW5jbHVkZXMgcGFyc25pcCwgaGFzIG90aGVyIHBhY2thZ2VzIGZvciBtYW55IG9mIHRoZXNlIHRhc2tzLCBhbmQgdGhleSBhcmUgZGVzaWduZWQgdG8gd29yayB0b2dldGhlci4gV2UgYXJlIHdvcmtpbmcgdG93YXJkcyBoaWdoZXItbGV2ZWwgQVBJcyB0aGF0IGNhbiByZXBsaWNhdGUgYW5kIGV4dGVuZCB3aGF0IHRoZSBjdXJyZW50IG1vZGVsIHBhY2thZ2VzIGNhbiBkby4iCgpUaGVyZSBhcmUgbWFueSBSIHBhY2thZ2VzIGluIHRoZSBgdGlkeW1vZGVsc2AgZWNvc3lzdGVtLCB3aGljaCBhc3Npc3Qgd2l0aCB2YXJpb3VzIHN0ZXBzIGluIHRoZSBwcm9jZXNzIG9mIGJ1aWxkaW5nIGEgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG0uIFRoZXNlIGFyZSB0aGUgbWFpbiBwYWNrYWdlcywgYnV0IHRoZXJlIGFyZSBvdGhlcnMuCgpgYGB7ciwgZWNobz1GQUxTRSwgb3V0LndpZHRoPSI4MDBweCJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsInNpbXBsZXRpZHltb2RlbHMucG5nIikpCmBgYAoKVGhpcyBpcyBhIHNjaGVtYXRpYyBvZiBob3cgdGhlc2UgcGFja2FnZXMgd29yayB0b2dldGhlciB0byBidWlsZCBhIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtOgoKYGBge3IsIGVjaG89RkFMU0UsIG91dC53aWR0aD0iODAwcHgifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWciLCJNYWNoaW5lTGVhcm5pbmcucG5nIikpCmBgYAoKIyMjIEJlbmVmaXRzIG9mIHRpZHltb2RlbHMgIAoKVGhlIHR3byBtYWpvciBiZW5lZml0cyBvZiBgdGlkeW1vZGVsc2AgYXJlOiAKCjEuIFN0YW5kYXJkaXplZCB3b3JrZmxvdy9mb3JtYXQvbm90YXRpb24gYWNyb3NzIGRpZmZlcmVudCB0eXBlcyBvZiBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMgIAoKRGlmZmVyZW50IG5vdGF0aW9ucyBhcmUgcmVxdWlyZWQgZm9yIGRpZmZlcmVudCBhbGdvcml0aG1zIGFzIHRoZSBhbGdvcml0aG1zIGhhdmUgYmVlbiBkZXZlbG9wZWQgYnkgZGlmZmVyZW50IHBlb3BsZS4gVGhpcyB3b3VsZCByZXF1aXJlIHRoZSBwYWluc3Rha2luZyBwcm9jZXNzIG9mIHJlZm9ybWF0dGluZyB0aGUgZGF0YSB0byBiZSBjb21wYXRpYmxlIHdpdGggZWFjaCBhbGdvcml0aG0gaWYgbXVsdGlwbGUgYWxnb3JpdGhtcyB3ZXJlIHRlc3RlZC4KCjIuIENhbiBlYXNpbHkgbW9kaWZ5IHByZS1wcm9jZXNzaW5nLCBhbGdvcml0aG0gY2hvaWNlLCBhbmQgaHlwZXItcGFyYW1ldGVyIHR1bmluZyBtYWtpbmcgb3B0aW1pemF0aW9uIGVhc3kgIAoKTW9kaWZ5aW5nIGEgcGllY2Ugb2YgdGhlIG92ZXJhbGwgcHJvY2VzcyBpcyBub3cgZWFzaWVyIHRoYW4gYmVmb3JlIGJlY2F1c2UgbWFueSBvZiB0aGUgc3RlcHMgYXJlIHNwZWNpZmllZCB1c2luZyB0aGUgYHRpZHltb2RlbHNgIHBhY2thZ2VzIGluIGEgY29udmVuaWVudCBtYW5uZXIuIFRodXMgdGhlIGVudGlyZSBwcm9jZXNzIGNhbiBiZSByZXJ1biBhZnRlciBhIHNpbXBsZSBjaGFuZ2UgdG8gcHJlLXByb2Nlc3Npbmcgd2l0aG91dCBtdWNoIGRpZmZpY3VsdHkuCgoKCiMjIFNwbGl0dGluZyB0aGUgZGF0YQoKVGhlIGZpcnN0IHN0ZXAgYWZ0ZXIgZGF0YSBleHBsb3JhdGlvbiBpbiBtYWNoaW5lIGxlYXJuaW5nIGFuYWx5c2lzIGlzIHRvIFtzcGxpdCB0aGUgZGF0YV0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL3RyYWluLXZhbGlkYXRpb24tYW5kLXRlc3Qtc2V0cy03MmNiNDBjYmE5ZTcpe3RhcmdldD0iX2JsYW5rIn0gaW50byAqKnRyYWluaW5nKiogYW5kICoqdGVzdGluZyoqIGRhdGEgc2V0cy4gCgpUaGUgdHJhaW5pbmcgZGF0YSBzZXQgd2lsbCBiZSB1c2VkIHRvIGJ1aWxkIGFuZCB0dW5lIG91ciBtb2RlbC4gClRoaXMgaXMgdGhlIGRhdGEgdGhhdCB0aGUgbW9kZWwgImxlYXJucyIgb24uIApUaGUgdGVzdGluZyBkYXRhIHNldCB3aWxsIGJlIHVzZWQgdG8gZXZhbHVhdGUgdGhlIHBlcmZvcm1hbmNlIG9mIG91ciBtb2RlbCBpbiBhIG1vcmUgZ2VuZXJhbGl6YWJsZSB3YXkuIFdoYXQgZG8gd2UgbWVhbiBieSAiZ2VuZXJhbGl6YWJsZSI/CgpSZW1lbWJlciB0aGF0IG91ciBtYWluIGdvYWwgaXMgdG8gdXNlIG91ciBtb2RlbCB0byBiZSBhYmxlIHRvIHByZWRpY3QgYWlyIHBvbGx1dGlvbiBsZXZlbHMgaW4gYXJlYXMgd2hlcmUgdGhlcmUgYXJlIG5vIGdyYXZpbWV0cmljIG1vbml0b3JzLiAKClRoZXJlZm9yZSwgaWYgb3VyIG1vZGVsIGlzIHJlYWxseSBnb29kIGF0IHByZWRpY3RpbmcgYWlyIHBvbGx1dGlvbiB3aXRoIHRoZSBkYXRhIHRoYXQgd2UgdXNlIHRvIGJ1aWxkIGl0LCBpdCBtaWdodCBub3QgZG8gdGhlIGJlc3Qgam9iIGZvciB0aGUgYXJlYXMgd2hlcmUgdGhlcmUgYXJlIGZldyB0byBubyBtb25pdG9ycy4gCgpUaGlzIHdvdWxkIGNhdXNlIHVzIHRvIGhhdmUgcmVhbGx5IGdvb2QgcHJlZGljdGlvbiBhY2N1cmFjeSBhbmQgd2UgbWlnaHQgYXNzdW1lIHRoYXQgd2Ugd2VyZSBnb2luZyB0byBkbyBhIGdvb2Qgam9iIGVzdGltYXRpbmcgYWlyIHBvbGx1dGlvbiBhbnkgdGltZSB3ZSB1c2Ugb3VyIG1vZGVsLCBidXQgaW4gZmFjdCB0aGlzIHdvdWxkIGxpa2VseSBub3QgYmUgdGhlIGNhc2UuIApUaGlzIHNpdHVhdGlvbiBpcyB3aGF0IHdlIGNhbGwgKipbb3ZlcmZpdHRpbmddKGh0dHBzOi8vdG93YXJkc2RhdGFzY2llbmNlLmNvbS90cmFpbi10ZXN0LXNwbGl0LWFuZC1jcm9zcy12YWxpZGF0aW9uLWluLXB5dGhvbi04MGI2MWJlY2E0YjYpe3RhcmdldD0iX2JsYW5rIn0gKiouCgpPdmVyZml0dGluZyBoYXBwZW5zIHdoZW4gd2UgZW5kIHVwIG1vZGVsaW5nIG5vdCBvbmx5IHRoZSBtYWpvciByZWxhdGlvbnNoaXBzIGluIG91ciBkYXRhIGJ1dCBhbHNvIHRoZSBub2lzZSB3aXRoaW4gb3VyIGRhdGEuIAoKYGBge3IsIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJodHRwczovL21pcm8ubWVkaXVtLmNvbS9tYXgvMTExMC8xKnRCRXJYWVZ2VHcyalNVWUs3dGhVMkEucG5nIikKYGBgCgojIyMjIyBbW3NvdXJjZV1dKGh0dHBzOi8vbWlyby5tZWRpdW0uY29tL21heC8xMTEwLzEqdEJFclhZVnZUdzJqU1VZSzd0aFUyQS5wbmcpe3RhcmdldD0iX2JsYW5rIn0KCklmIHdlIGdldCBnb29kIHByZWRpY3Rpb24gd2l0aCBvdXIgdGVzdGluZyBzZXQsIHRoZW4gd2Uga25vdyB0aGF0IG91ciBtb2RlbCBjYW4gYmUgYXBwbGllZCB0byBvdGhlciBkYXRhIGFuZCB3aWxsIGxpa2VseSBwZXJmb3JtIHdlbGwuIFdlIHdpbGwgZGlzY3VzcyB0aGlzIG1vcmUgbGF0ZXIuCgpXZSB3aWxsIG5vdCB0b3VjaCB0aGUgdGVzdGluZyBzZXQgdW50aWwgd2UgaGF2ZSBjb21wbGV0ZWQgb3B0aW1pemluZyBvdXIgbW9kZWwgd2l0aCB0aGUgdHJhaW5pbmcgc2V0LiAKVGhpcyB3aWxsIGFsbG93IHVzIHRvIGhhdmUgYSBsZXNzIGJpYXNlZCBldmFsdWF0aW9uIG9mIGhvdyB3ZWxsIG91ciBtb2RlbCBjYW4gZG8gd2l0aCBvdGhlciBkYXRhIGJlc2lkZXMgdGhlIGRhdGEgdXNlZCBpbiB0aGUgdHJhaW5pbmcgc2V0IHRvIGJ1aWxkIHRoZSBtb2RlbC4gCklkZWFsbHksIHlvdSB3b3VsZCBhbHNvIHdhbnQgYSBjb21wbGV0ZWx5IGluZGVwZW5kZW50IGRhdGEgc2V0IHRvIGZ1cnRoZXIgdGVzdCB0aGUgcGVyZm9ybWFuY2Ugb2YgeW91ciBtb2RlbC4KClRvIHNwbGl0IHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3RpbmcsIHdlIHdpbGwgdXNlIHRoZSBgaW5pdGlhbF9zcGxpdCgpYCBmdW5jdGlvbiBpbiB0aGUgYHJzYW1wbGVgIHBhY2thZ2UgdG8gc3BlY2lmeSBob3cgd2Ugd2FudCB0byBzcGxpdCBvdXIgZGF0YS4KCgpgYGB7ciwgZWNobz1GQUxTRX0KCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsInNwbGl0LnBuZyIpKQpgYGAKCmBgYHtyfQpzZXQuc2VlZCgxMjM0KQpwbV9zcGxpdCA8LSByc2FtcGxlOjppbml0aWFsX3NwbGl0KGRhdGEgPSBwbSwgcHJvcCA9IDIvMykKcG1fc3BsaXQKYGBgCgpBIGNvdXBsZSBvZiBub3RlcyBmcm9tIHRoZSBjb2RlIGFib3ZlOiAKCi0gVHlwaWNhbGx5LCBkYXRhIGFyZSBzcGxpdCBpbnRvIDMvNCBvZiB0aGUgb2JzZXJ2YXRpb25zIGZvciB0cmFpbmluZyBhbmQgMS80IGZvciB0ZXN0aW5nLiBUaGlzIGlzIHRoZSBkZWZhdWx0IHByb3BvcnRpb24gYW5kIGRvZXMgbm90IG5lZWQgdG8gYmUgc3BlY2lmaWVkLiBIb3dldmVyLCB5b3UgY2FuIGNoYW5nZSB0aGUgcHJvcG9ydGlvbiB1c2luZyB0aGUgYHByb3BgIGFyZ3VtZW50LCB3aGljaCB3ZSB3aWxsIGRvIHRoYXQgaGVyZSBmb3IgaWxsdXN0cmF0aXZlIHB1cnBvc2VzLgotIFNpbmNlIHRoZSBzcGxpdCBpcyBwZXJmb3JtZWQgcmFuZG9tbHksIGl0IGlzIGEgZ29vZCBpZGVhIHRvIHVzZSB0aGUgYHNldC5zZWVkKClgIGZ1bmN0aW9uIGluIGJhc2UgUiB0byBlbnN1cmUgdGhhdCBpZiB5b3VyIHJlcnVuIHlvdXIgY29kZSB0aGF0IHlvdXIgc3BsaXQgd2lsbCBiZSB0aGUgc2FtZSBuZXh0IHRpbWUuCi0gV2UgY2FuIHNlZSB0aGUgbnVtYmVyIG9mIG1vbml0b3JzIGluIG91ciB0cmFpbmluZywgdGVzdGluZywgYW5kIG9yaWdpbmFsIGRhdGEgYnkgdHlwaW5nIGluIHRoZSBuYW1lIG9mIG91ciBzcGxpdCBvYmplY3QuIFRoZSByZXN1bHQgd2lsbCBsb29rIGxpa2UgdGhpczoKPHRyYWluaW5nIGRhdGEgc2FtcGxlIG51bWJlciwgdGVzdGluZyBkYXRhIHNhbXBsZSBudW1iZXIsIG9yaWdpbmFsIHNhbXBsZSBudW1iZXI+IAoKTm93LCB5b3UgY2FuIGFsc28gc3BlY2lmeSBhIHZhcmlhYmxlIHRvIHN0cmF0aWZ5IGJ5IHdpdGggdGhlIGBzdHJhdGFgIGFyZ3VtZW50LiAKVGhpcyBpcyB1c2VmdWwgaWYgeW91IGhhdmUgaW1iYWxhbmNlZCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYW5kIHlvdSB3b3VsZCBsaWtlIHRvIGludGVudGlvbmFsbHkgbWFrZSBzdXJlIHRoYXQgdGhlcmUgYXJlIHNpbWlsYXIgbnVtYmVyIG9mIHNhbXBsZXMgb2YgdGhlIHJhcmVyIGNhdGVnb3JpZXMgaW4gYm90aCB0aGUgdGVzdGluZyBhbmQgdHJhaW5pbmcgc2V0cy4gCk90aGVyd2lzZSB0aGUgc3BsaXQgaXMgcGVyZm9ybWVkIHJhbmRvbWx5LiAKCkFjY29yZGluZyB0byB0aGUgW2RvY3VtZW50YXRpb25dKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9yc2FtcGxlL3ZlcnNpb25zLzAuMC41L3RvcGljcy9pbml0aWFsX3NwbGl0KSBmb3IgdGhlIGByc2FtcGxlYCBwYWNrYWdlOgoKPiBUaGUgc3RyYXRhIGFyZ3VtZW50IGNhdXNlcyB0aGUgcmFuZG9tIHNhbXBsaW5nIHRvIGJlIGNvbmR1Y3RlZCB3aXRoaW4gdGhlIHN0cmF0aWZpY2F0aW9uIHZhcmlhYmxlLiBUaGlzIGNhbiBoZWxwIGVuc3VyZSB0aGF0IHRoZSBudW1iZXIgb2YgZGF0YSBwb2ludHMgaW4gdGhlIHRyYWluaW5nIGRhdGEgaXMgZXF1aXZhbGVudCB0byB0aGUgcHJvcG9ydGlvbnMgaW4gdGhlIG9yaWdpbmFsIGRhdGEgc2V0LgoKSW4gdGhlIGNhc2Ugd2l0aCBvdXIgZGF0YSBzZXQsIHBlcmhhcHMgd2Ugd291bGQgbGlrZSBvdXIgdHJhaW5pbmcgc2V0IHRvIGhhdmUgc2ltaWxhciBwcm9wb3J0aW9ucyBvZiBtb25pdG9ycyBmcm9tIGVhY2ggb2YgdGhlIHN0YXRlcyBhcyBpbiB0aGUgaW5pdGlhbCBkYXRhLiAKVGhpcyBtaWdodCBiZSB1c2VmdWwgaWYgd2Ugd2FudCBvdXIgbW9kZWwgdG8gYmUgZ2VuZXJhbGl6YWJsZSBhY3Jvc3MgYWxsIG9mIHRoZSBzdGF0ZXMuCgpXZSBjYW4gc2VlIHRoYXQgaW5kZWVkIHRoZXJlIGFyZSBkaWZmZXJlbnQgcHJvcG9ydGlvbnMgb2YgbW9uaXRvcnMgaW4gZWFjaCBzdGF0ZSBieSB1c2luZyB0aGUgYGNvdW50KClgIGZ1bmN0aW9uIG9mIHRoZSBgZHBseXJgIHBhY2thZ2UuIAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KY291bnQocG0sIHN0YXRlKQpgYGAKClNjcm9sbCB0aHJvdWdoIHRoZSBvdXRwdXQ6CgojIyMjIHsuc2Nyb2xsYWJsZSB9CgpgYGB7ciwgZWNobz1GQUxTRX0KIyBTY3JvbGwgdGhyb3VnaCB0aGUgb3V0cHV0IQpjb3VudChwbSwgc3RhdGUpICU+JQogIHByaW50KG4gPSAxZTMpCmBgYAojIyMjCgpJZiBvdXIgZGF0YSBzZXQgd2VyZSBsYXJnZSBlbm91Z2ggaXQgbWlnaHQgYmUgbmljZSB0aGVuIHRvIHN0cmF0aWZ5IGJ5IHN0YXRlIHVzaW5nIHRoZSBgc3RyYXRhID0gInN0YXRlImAgYXJndW1lbnQgaW4gYGluaXRpYWxfc3BsaXQoKWAsIGJ1dCBvdXIgZGF0YSBpcyB1bmZvcnR1bmF0ZWx5IG5vdCBsYXJnZSBlbm91Z2guIAoKSW1wb3J0YW50bHkgdGhlIGBpbml0aWFsX3NwbGl0YCBmdW5jdGlvbiBvbmx5IGRldGVybWluZXMgd2hhdCByb3dzIG9mIG91ciBgcG1gIGRhdGEgZnJhbWUgc2hvdWxkIGJlIGFzc2lnbmVkIGZvciB0cmFpbmluZyBvciB0ZXN0aW5nLCBpdCBkb2VzIG5vdCBhY3R1YWxseSBzcGxpdCB0aGUgZGF0YS4gCgpUbyBleHRyYWN0IHRoZSB0ZXN0aW5nIGFuZCB0cmFpbmluZyBkYXRhIHdlIGNhbiB1c2UgdGhlIGB0cmFpbmluZygpYCBhbmQgYHRlc3RpbmcoKWAgZnVuY3Rpb25zIGFsc28gb2YgdGhlIGByc2FtcGxlYCBwYWNrYWdlLgoKIyMjIyB7LnNjcm9sbGFibGUgfQpgYGB7cn0KdHJhaW5fcG0gPC1yc2FtcGxlOjp0cmFpbmluZyhwbV9zcGxpdCkKdGVzdF9wbSA8LXJzYW1wbGU6OnRlc3RpbmcocG1fc3BsaXQpCiAKIyBTY3JvbGwgdGhyb3VnaCB0aGUgb3V0cHV0IQpjb3VudCh0cmFpbl9wbSwgc3RhdGUpCmNvdW50KHRlc3RfcG0sIHN0YXRlKQpgYGAKIyMjIwoKCgojIyBQcmVwYXJpbmcgZm9yIHByZS1wcm9jZXNzaW5nIHRoZSBkYXRhCgpBZnRlciBzcGxpdHRpbmcgdGhlIGRhdGEsIHRoZSBuZXh0IHN0ZXAgaXMgdG8gcHJvY2VzcyB0aGUgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YSBzbyB0aGF0IHRoZSBkYXRhIGFyZSBhcmUgY29tcGF0aWJsZSBhbmQgb3B0aW1pemVkIHRvIGJlIHVzZWQgd2l0aCB0aGUgbW9kZWwuIApUaGlzIGludm9sdmVzIGFzc2lnbmluZyB2YXJpYWJsZXMgdG8gc3BlY2lmaWMgcm9sZXMgd2l0aGluIHRoZSBtb2RlbCBhbmQgcHJlLXByb2Nlc3NpbmcgbGlrZSBzY2FsaW5nIHZhcmlhYmxlcyBhbmQgcmVtb3ZpbmcgcmVkdW5kYW50IHZhcmlhYmxlcy4gClRoaXMgcHJvY2VzcyBpcyBhbHNvIGNhbGxlZCBmZWF0dXJlIGVuZ2luZWVyaW5nLgoKVG8gZG8gdGhpcyBpbiBgdGlkeW1vZGVsc2AsIHdlIHdpbGwgY3JlYXRlIHdoYXQncyBjYWxsZWQgYSAicmVjaXBlIiB1c2luZyB0aGUgYHJlY2lwZXNgIHBhY2thZ2UsIHdoaWNoIGlzIGEgc3RhbmRhcmRpemVkIGZvcm1hdCBmb3IgYSBzZXF1ZW5jZSBvZiBzdGVwcyBmb3IgcHJlLXByb2Nlc3NpbmcgdGhlIGRhdGEuClRoaXMgY2FuIGJlIHZlcnkgdXNlZnVsIGJlY2F1c2UgaXQgbWFrZXMgdGVzdGluZyBvdXQgZGlmZmVyZW50IHByZS1wcm9jZXNzaW5nIHN0ZXBzIG9yIGRpZmZlcmVudCBhbGdvcml0aG1zIHdpdGggdGhlIHNhbWUgcHJlLXByb2Nlc3NpbmcgdmVyeSBlYXN5IGFuZCByZXByb2R1Y2libGUuCkNyZWF0aW5nIGEgcmVjaXBlIHNwZWNpZmllcyAqKmhvdyBhIGRhdGEgZnJhbWUgb2YgcHJlZGljdG9ycyBzaG91bGQgYmUgY3JlYXRlZCoqIC0gaXQgc3BlY2lmaWVzIHdoYXQgdmFyaWFibGVzIHRvIGJlIHVzZWQgYW5kIHRoZSBwcmUtcHJvY2Vzc2luZyBzdGVwcywgYnV0IGl0ICoqZG9lcyBub3QgZXhlY3V0ZSB0aGVzZSBzdGVwcyoqIG9yIGNyZWF0ZSB0aGUgZGF0YSBmcmFtZSBvZiBwcmVkaWN0b3JzLgoKIyMjIFN0ZXAgMTogU3BlY2lmeSB2YXJpYWJsZXMgcm9sZXMgd2l0aCBgcmVjaXBlKClgIGZ1bmN0aW9uCgpUaGUgZmlyc3QgdGhpbmcgdG8gZG8gdG8gY3JlYXRlIGEgcmVjaXBlIGlzIHRvIHNwZWNpZnkgd2hpY2ggdmFyaWFibGVzIHdlIHdpbGwgYmUgdXNpbmcgYXMgb3VyIG91dGNvbWUgYW5kIHByZWRpY3RvcnMgdXNpbmcgdGhlIGByZWNpcGUoKWAgZnVuY3Rpb24uIApJbiB0ZXJtcyBvZiB0aGUgbWV0YXBob3Igb2YgYmFraW5nLCB3ZSBjYW4gdGhpbmsgb2YgdGhpcyBhcyBsaXN0aW5nIG91ciBpbmdyZWRpZW50cy4gClRyYW5zbGF0aW5nIHRoaXMgdG8gdGhlIGByZWNpcGVzYCBwYWNrYWdlLCB3ZSB1c2UgdGhlIGByZWNpcGUoKWAgZnVuY3Rpb24gdG8gYXNzaWduIHJvbGVzIHRvIGFsbCB0aGUgdmFyaWFibGVzLiAKCkxldCdzIHRyeSB0aGUgc2ltcGxlc3QgcmVjaXBlIHdpdGggbm8gcHJlLXByb2Nlc3Npbmcgc3RlcHM6IHNpbXBseSBsaXN0IHRoZSBvdXRjb21lIGFuZCBwcmVkaWN0b3IgdmFyaWFibGVzLgoKV2UgY2FuIGRvIHNvIGluIHR3byB3YXlzOiAgCgoxKSBVc2luZyBmb3JtdWxhIG5vdGF0aW9uICAKMikgQXNzaWduaW5nIHJvbGVzIHRvIGVhY2ggdmFyaWFibGUgIAoKTGV0J3MgbG9vayBhdCB0aGUgZmlyc3Qgd2F5IHVzaW5nIGZvcm11bGEgbm90YXRpb24sIHdoaWNoIGxvb2tzIGxpa2UgdGhpczogIAoKb3V0Y29tZShzKSB+IHByZWRpY3RvcihzKSAgCgpJZiBpbiB0aGUgY2FzZSBvZiBtdWx0aXBsZSBwcmVkaWN0b3JzIG9yIGEgbXVsdGl2YXJpYXRlIHNpdHVhdGlvbiB3aXRoIHR3byBvdXRjb21lcywgdXNlIGEgcGx1cyBzaWduOiAgCgpvdXRjb21lMSArIG91dGNvbWUyIH4gcHJlZGljdG9yMSArIHByZWRpY3RvcjIgIAoKSWYgd2Ugd2FudCB0byBpbmNsdWRlIGFsbCBwcmVkaWN0b3JzIHdlIGNhbiB1c2UgYSBwZXJpb2QgbGlrZSBzbzogIAoKb3V0Y29tZV92YXJpYWJsZV9uYW1lIH4gLiAgCgpOb3cgd2l0aCBvdXIgZGF0YSwgd2Ugd2lsbCBzdGFydCBieSBtYWtpbmcgYSByZWNpcGUgZm9yIG91ciB0cmFpbmluZyBkYXRhLgpJZiB5b3UgcmVjYWxsLCB0aGUgY29udGludW91cyBvdXRjb21lIHZhcmlhYmxlIGlzIGB2YWx1ZWAgKHRoZSBhdmVyYWdlIGFubnVhbCBncmF2aW1ldHJpYyBtb25pdG9yIFBNfjIuNX4gY29uY2VudHJhdGlvbiBpbiB1Zy9tXjNeKS4gCk91ciBmZWF0dXJlcyAob3IgcHJlZGljdG9yIHZhcmlhYmxlcykgYXJlIGFsbCB0aGUgb3RoZXIgdmFyaWFibGVzIGV4Y2VwdCB0aGUgbW9uaXRvciBJRCwgd2hpY2ggaXMgYW4gYGlkYCB2YXJpYWJsZS4KClRoZSByZWFzb24gbm90IHRvIGluY2x1ZGUgdGhlIGBpZGAgdmFyaWFibGUgaXMgYmVjYXVzZSB0aGlzIHZhcmlhYmxlIGluY2x1ZGVzIHRoZSBjb3VudHkgbnVtYmVyIGFuZCBhIG51bWJlciBkZXNpZ25hdGluZyB3aGljaCBwYXJ0aWN1bGFyIG1vbml0b3IgdGhlIHZhbHVlcyBjYW1lIGZyb20gKG9mIHRoZSBtb25pdG9ycyB0aGVyZSBhcmUgaW4gdGhhdCBjb3VudHkpLiAKU2luY2UgdGhpcyBudW1iZXIgaXMgYXJiaXRyYXJ5IGFuZCB0aGUgY291bnR5IGluZm9ybWF0aW9uIGlzIGFsc28gZ2l2ZW4gaW4gdGhlIGRhdGEsIGFuZCB0aGUgZmFjdCB0aGF0IGVhY2ggbW9uaXRvciBvbmx5IGhhcyBvbmUgdmFsdWUgaW4gdGhlIGB2YWx1ZWAgdmFyaWFibGUsIG5vdGhpbmcgaXMgZ2FpbmVkIGJ5IGluY2x1ZGluZyB0aGlzIHZhcmlhYmxlIGFuZCBpdCBtYXkgaW5zdGVhZCBpbnRyb2R1Y2Ugbm9pc2UuIApIb3dldmVyLCBpdCBpcyB1c2VmdWwgdG8ga2VlcCB0aGlzIGRhdGEgdG8gdGFrZSBhIGxvb2sgYXQgd2hhdCBpcyBoYXBwZW5pbmcgbGF0ZXIuIApXZSB3aWxsIHNob3cgeW91IHdoYXQgdG8gZG8gaW4gdGhpcyBjYXNlIGluIGp1c3QgYSBiaXQuCgpJbiB0aGUgc2ltcGxlc3QgY2FzZSwgd2UgbWlnaHQgdXNlIGFsbCBwcmVkaWN0b3JzIGxpa2UgdGhpczoKCmBgYHtyfQpzaW1wbGVfcmVjIDwtIHRyYWluX3BtICU+JQogIHJlY2lwZXM6OnJlY2lwZSh2YWx1ZSB+IC4pCgpzaW1wbGVfcmVjCmBgYAoKV2Ugc2VlIGEgcmVjaXBlIGhhcyBiZWVuIGNyZWF0ZWQgd2l0aCAxIG91dGNvbWUgdmFyaWFibGUgYW5kIDQ5IHByZWRpY3RvciB2YXJpYWJsZXMgKG9yIGZlYXR1cmVzKS4gCkFsc28sIG5vdGljZSBob3cgd2UgbmFtZWQgdGhlIG91dHB1dCBvZiBgcmVjaXBlKClgLiAKVGhlIG5hbWluZyBjb252ZW50aW9uIGZvciByZWNpcGUgb2JqZWN0cyBpcyBgKl9yZWNgIG9yIGByZWNgLiAKCk5vdywgbGV0J3MgZ2V0IGJhY2sgdG8gdGhlIGBpZGAgdmFyaWFibGUuIApJbnN0ZWFkIG9mIGluY2x1ZGluZyBpdCBhcyBhIHByZWRpY3RvciB2YXJpYWJsZSwgd2UgY291bGQgYWxzbyB1c2UgdGhlIGB1cGRhdGVfcm9sZSgpYCBmdW5jdGlvbiBvZiB0aGUgYHJlY2lwZXNgIHBhY2thZ2UuCgpgYGB7cn0Kc2ltcGxlX3JlYyA8LSB0cmFpbl9wbSAlPiUKICByZWNpcGVzOjpyZWNpcGUodmFsdWUgfiAuKSAlPiUKICByZWNpcGVzOjp1cGRhdGVfcm9sZShpZCwgbmV3X3JvbGUgPSAiaWQgdmFyaWFibGUiKQoKc2ltcGxlX3JlYwpgYGAKCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayBoZXJlIGxlYXJuIG1vcmUgYWJvdXQgdGhlIHdvcmtpbmcgd2l0aCBgaWRgIHZhcmlhYmxlcyA8L3N1bW1hcnk+CgpUaGlzIG9wdGlvbiB3b3JrcyB3ZWxsIHdpdGggdGhlIG5ld2VyIGB3b3JrZmxvd3NgIHBhY2thZ2UsIGhvd2V2ZXIgYGlkYCB2YXJpYWJsZXMgYXJlIG9mdGVuIGRyb3BwZWQgZnJvbSBhbmFseXNlcyB0aGF0IGRvIG5vdCB1c2UgdGhpcyBuZXdlciBwYWNrYWdlIGFzIHRoZXkgY2FuIG1ha2UgdGhlIHByb2Nlc3MgZGlmZmljdWx0IHdpdGggdXNpbmcgdGhlIGBwYXJzbmlwYCBwYWNrYWdlIGFsb25lIGR1ZSB0byB0aGUgZmFjdCB0aGF0IG5ldyBsZXZlbHMgKG9yIHBvc3NpYmxlIHZhbHVlcykgbWF5IGJlIGludHJvZHVjZWQgd2l0aCB0aGUgdGVzdGluZyBkYXRhLgoKPC9kZXRhaWxzPgoKV2UgY291bGQgYWxzbyBzcGVjaWZ5IHRoZSBvdXRjb21lIGFuZCBwcmVkaWN0b3JzIGluIHRoZSBzYW1lIHdheSBhcyB3ZSBqdXN0IHNwZWNpZmllZCB0aGUgaWQgdmFyaWFibGUuIApQbGVhc2Ugc2VlIFtoZXJlXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3JlY2lwZXMvcmVmZXJlbmNlL3JlY2lwZS5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9IGZvciBleGFtcGxlcyBvZiBvdGhlciByb2xlcyBmb3IgdmFyaWFibGVzLiAKVGhlIHJvbGUgY2FuIGJlIGFjdHVhbGx5IGJlIGFueSB2YWx1ZS4gCgpUaGUgb3JkZXIgaXMgaW1wb3J0YW50IGhlcmUsIGFzIHdlIGZpcnN0IG1ha2UgYWxsIHZhcmlhYmxlcyBwcmVkaWN0b3JzIGFuZCB0aGVuIG92ZXJyaWRlIHRoaXMgcm9sZSBmb3IgdGhlIG91dGNvbWUgYW5kIGBpZGAgdmFyaWFibGUuIApXZSB3aWxsIHVzZSB0aGUgYGV2ZXJ5dGhpbmcoKWAgZnVuY3Rpb24gb2YgdGhlIGBkcGx5cmAgcGFja2FnZSB0byBzdGFydCB3aXRoIGFsbCBvZiB0aGUgdmFyaWFibGVzIGluIGB0cmFpbl9wbWAuCgpgYGB7cn0Kc2ltcGxlX3JlYyA8LXJlY2lwZSh0cmFpbl9wbSkgJT4lCiAgICB1cGRhdGVfcm9sZShldmVyeXRoaW5nKCksIG5ld19yb2xlID0gInByZWRpY3RvciIpJT4lCiAgICB1cGRhdGVfcm9sZSh2YWx1ZSwgbmV3X3JvbGUgPSAib3V0Y29tZSIpJT4lCiAgICB1cGRhdGVfcm9sZShpZCwgbmV3X3JvbGUgPSAiaWQgdmFyaWFibGUiKQoKc2ltcGxlX3JlYwpgYGAKCklmIHdlIHdhbnQgdG8gdGFrZSBhIGxvb2sgYXQgb3VyIGZvcm11bGEgZnJvbSBvdXIgcmVjaXBlLCB3ZSBjYW4gZG8gdXNlIHRoZSBgZm9ybXVsYSgpYCBmdW5jdGlvbiBvZiB0aGUgYHN0YXRzYCBwYWNrYWdlLgoKYGBge3J9CmZvcm11bGEoc2ltcGxlX3JlYykKYGBgCgpXZSBjYW4gYWxzbyB2aWV3IG91ciByZWNpcGUgaW4gbW9yZSBkZXRhaWwgdXNpbmcgdGhlIGJhc2UgYHN1bW1hcnkoKWAgZnVuY3Rpb24uCgpgYGB7cn0Kc3VtbWFyeShzaW1wbGVfcmVjKQpgYGAKClRvIHN1bW1hcml6ZSB0aGlzIHN0ZXAsIHdlIHVzZSB0aGUgYHJlY2lwZSgpYCBmdW5jdGlvbiB0byBhc3NpZ24gcm9sZXMgdG8gYWxsIHRoZSB2YXJpYWJsZXM6IAoKYGBge3IsIGVjaG89RkFMU0UsIG91dC53aWR0aD0iNDAwcHgifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWciLCJTdGFydGluZ19hX3JlY2lwZV9yZWNpcGVzMS5wbmciKSkKYGBgCgoKIyMjIFN0ZXAgMjogU3BlY2lmeSB0aGUgcHJlLXByb2Nlc3Npbmcgc3RlcHMgd2l0aCBgc3RlcCooKWAgZnVuY3Rpb25zCgpOZXh0LCB3ZSB1c2UgdGhlIGBzdGVwKigpYCBmdW5jdGlvbnMgZnJvbSB0aGUgYHJlY2lwZWAgcGFja2FnZSB0byBzcGVjaWZ5IHByZS1wcm9jZXNzaW5nIHN0ZXBzLiAKCmBgYHtyLCBlY2hvPUZBTFNFLCBvdXQud2lkdGg9IjQwMHB4In0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwiTWFraW5nX2FfcmVjaXBlX3JlY2lwZXMyLnBuZyIpKQpgYGAKCioqVGhpcyBbbGlua10oaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby9yZWNpcGVzL3JlZmVyZW5jZS9pbmRleC5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9IGFuZCB0aGlzIFtsaW5rXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcmVjaXBlcy9yZWNpcGVzLnBkZil7dGFyZ2V0PSJfYmxhbmsifSBzaG93IHRoZSBtYW55IG9wdGlvbnMgZm9yIHJlY2lwZSBzdGVwIGZ1bmN0aW9ucy4qKgoKPHU+VGhlcmUgYXJlIHN0ZXAgZnVuY3Rpb25zIGZvciBhIHZhcmlldHkgb2YgcHVycG9zZXM6PC91PgoKMS4gWyoqSW1wdXRhdGlvbioqXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9JbXB1dGF0aW9uXyhzdGF0aXN0aWNzKSl7dGFyZ2V0PSJfYmxhbmsifSAtLSBmaWxsaW5nIGluIG1pc3NpbmcgdmFsdWVzIGJhc2VkIG9uIHRoZSBleGlzdGluZyBkYXRhIAoyLiBbKipUcmFuc2Zvcm1hdGlvbioqXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9EYXRhX3RyYW5zZm9ybWF0aW9uXyhzdGF0aXN0aWNzKSl7dGFyZ2V0PSJfYmxhbmsifSAtLSBjaGFuZ2luZyBhbGwgdmFsdWVzIG9mIGEgdmFyaWFibGUgaW4gdGhlIHNhbWUgd2F5LCB0eXBpY2FsbHkgdG8gbWFrZSBpdCBtb3JlIG5vcm1hbCBvciBlYXNpZXIgdG8gaW50ZXJwcmV0CjMuIFsqKkRpc2NyZXRpemF0aW9uKipdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0Rpc2NyZXRpemF0aW9uX29mX2NvbnRpbnVvdXNfZmVhdHVyZXMpe3RhcmdldD0iX2JsYW5rIn0gLS0gY29udmVydGluZyBjb250aW51b3VzIHZhbHVlcyBpbnRvIGRpc2NyZXRlIG9yIG5vbWluYWwgdmFsdWVzIC0gYmlubmluZyBmb3IgZXhhbXBsZSB0byByZWR1Y2UgdGhlIG51bWJlciBvZiBwb3NzaWJsZSBsZXZlbHMgKEhvd2V2ZXIgdGhpcyBpcyBnZW5lcmFsbHkgbm90IGFkdmlzYWJsZSEpCjQuIFsqKkVuY29kaW5nIC8gQ3JlYXRpbmcgRHVtbXkgVmFyaWFibGVzKipdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0R1bW15X3ZhcmlhYmxlXyhzdGF0aXN0aWNzKSl7dGFyZ2V0PSJfYmxhbmsifSAtLSBjcmVhdGluZyBhIG51bWVyaWMgY29kZSBmb3IgY2F0ZWdvcmljYWwgdmFyaWFibGVzCihbKipNb3JlIG9uIER1bW15IFZhcmlhYmxlcyBhbmQgb25lIGhvdCBlbmNvZGluZyoqXShodHRwczovL21lZGl1bS5jb20vcC9iNTg0MGJlM2M0MWEvcmVzcG9uc2VzL3Nob3cpe3RhcmdldD0iX2JsYW5rIn0pCjUuIFsqKkRhdGEgdHlwZSBjb252ZXJzaW9ucyoqXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvaGFibGFyL3ZpZ25ldHRlcy9jb252ZXJ0Lmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gIC0tIHdoaWNoIG1lYW5zIGNoYW5naW5nIGZyb20gaW50ZWdlciB0byBmYWN0b3Igb3IgbnVtZXJpYyB0byBkYXRlIGV0Yy4KNi4gWyoqSW50ZXJhY3Rpb24qKl0oaHR0cHM6Ly9zdGF0aXN0aWNzYnlqaW0uY29tL3JlZ3Jlc3Npb24vaW50ZXJhY3Rpb24tZWZmZWN0cy8pe3RhcmdldD0iX2JsYW5rIn0gIHRlcm0gYWRkaXRpb24gdG8gdGhlIG1vZGVsIC0tIHdoaWNoIG1lYW5zIHRoYXQgd2Ugd291bGQgYmUgbW9kZWxpbmcgZm9yIHByZWRpY3RvcnMgdGhhdCB3b3VsZCBpbmZsdWVuY2UgdGhlIGNhcGFjaXR5IG9mIGVhY2ggb3RoZXIgdG8gcHJlZGljdCB0aGUgb3V0Y29tZQo3LiBbKipOb3JtYWxpemF0aW9uKipdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL05vcm1hbGl6YXRpb25fKHN0YXRpc3RpY3MpKXt0YXJnZXQ9Il9ibGFuayJ9IC0tIGNlbnRlcmluZyBhbmQgc2NhbGluZyB0aGUgZGF0YSB0byBhIHNpbWlsYXIgcmFuZ2Ugb2YgdmFsdWVzCjguIFsqKkRpbWVuc2lvbmFsaXR5IFJlZHVjdGlvbi8gU2lnbmFsIEV4dHJhY3Rpb24qKl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRGltZW5zaW9uYWxpdHlfcmVkdWN0aW9uKXt0YXJnZXQ9Il9ibGFuayJ9IC0tIHJlZHVjaW5nIHRoZSBzcGFjZSBvZiBmZWF0dXJlcyBvciBwcmVkaWN0b3JzIHRvIGEgc21hbGxlciBzZXQgb2YgdmFyaWFibGVzIHRoYXQgY2FwdHVyZSB0aGUgdmFyaWF0aW9uIG9yIHNpZ25hbCBpbiB0aGUgb3JpZ2luYWwgdmFyaWFibGVzIChleC4gUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcyBhbmQgSW5kZXBlbmRlbnQgQ29tcG9uZW50IEFuYWx5c2lzKQo5LiAqKkZpbHRlcmluZyoqIC0tIGZpbHRlcmluZyBvcHRpb25zIGZvciByZW1vdmluZyB2YXJpYWJsZXMgKGV4LiByZW1vdmUgdmFyaWFibGVzIHRoYXQgYXJlIGhpZ2hseSBjb3JyZWxhdGVkIHRvIG90aGVycyBvciByZW1vdmUgdmFyaWFibGVzIHdpdGggdmVyeSBsaXR0bGUgdmFyaWFuY2UgYW5kIHRoZXJlZm9yZSBsaWtlbHkgbGl0dGxlIHByZWRpY3RpdmUgY2FwYWNpdHkpCjEwLiBbKipSb3cgb3BlcmF0aW9ucyoqXShodHRwczovL3RhcnRhcnVzLm9yZy9nYXJldGgvbWF0aHMvTGluZWFyX0FsZ2VicmEvcm93X29wZXJhdGlvbnMucGRmKXt0YXJnZXQ9Il9ibGFuayJ9IC0tIHBlcmZvcm1pbmcgZnVuY3Rpb25zIG9uIHRoZSB2YWx1ZXMgd2l0aGluIHRoZSByb3dzICAoZXguIHJlYXJyYW5naW5nLCBmaWx0ZXJpbmcsIGltcHV0aW5nKQoxMS4gKipDaGVja2luZyBmdW5jdGlvbnMqKiAtLSBTYW5pdHkgY2hlY2tzIHRvIGxvb2sgZm9yIG1pc3NpbmcgdmFsdWVzLCB0byBsb29rIGF0IHRoZSB2YXJpYWJsZSBjbGFzc2VzIGV0Yy4KCkFsbCBvZiB0aGUgc3RlcCBmdW5jdGlvbnMgbG9vayBsaWtlIGBzdGVwXyooKWAgd2l0aCB0aGUgYCpgIHJlcGxhY2VkIHdpdGggYSBuYW1lLCBleGNlcHQgZm9yIHRoZSBjaGVjayBmdW5jdGlvbnMgd2hpY2ggbG9vayBsaWtlIGBjaGVja18qKClgLgoKVGhlcmUgYXJlIHNldmVyYWwgd2F5cyB0byBzZWxlY3Qgd2hhdCB2YXJpYWJsZXMgdG8gYXBwbHkgc3RlcHMgdG86ICAKCjEuIFVzaW5nIGB0aWR5c2VsZWN0YCBtZXRob2RzOiBgY29udGFpbnMoKWAsIGBtYXRjaGVzKClgLCBgc3RhcnRzX3dpdGgoKWAsIGBlbmRzX3dpdGgoKWAsIGBldmVyeXRoaW5nKClgLCBgbnVtX3JhbmdlKClgICAKMi4gVXNpbmcgdGhlIHR5cGU6IGBhbGxfbm9taW5hbCgpYCwgYGFsbF9udW1lcmljKClgICwgYGhhc190eXBlKClgIAozLiBVc2luZyB0aGUgcm9sZTogYGFsbF9wcmVkaWN0b3JzKClgLCBgYWxsX291dGNvbWVzKClgLCBgaGFzX3JvbGUoKWAKNC4gVXNpbmcgdGhlIG5hbWUgLSB1c2UgdGhlIGFjdHVhbCBuYW1lIG9mIHRoZSB2YXJpYWJsZS92YXJpYWJsZXMgb2YgaW50ZXJlc3QgIAoKTGV0J3MgdHJ5IGFkZGluZyBzb21lIHN0ZXBzIHRvIG91ciByZWNpcGUuCgoKV2UgbWlnaHQgd2FudCB0byBwb3RlbnRpYWxseSBbb25lIGhvdCBlbmNvZGVdKGh0dHBzOi8vbWFjaGluZWxlYXJuaW5nbWFzdGVyeS5jb20vd2h5LW9uZS1ob3QtZW5jb2RlLWRhdGEtaW4tbWFjaGluZS1sZWFybmluZy8pe3RhcmdldD0iX2JsYW5rIn0gc29tZSBvZiBvdXIgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHNvIHRoYXQgdGhleSBjYW4gYmUgdXNlZCB3aXRoIGNlcnRhaW4gYWxnb3JpdGhtcy4gCgpXZSBjYW4gZG8gdGhpcyB3aXRoIHRoZSBgc3RlcF9kdW1teSgpYCBmdW5jdGlvbiBhbmQgdGhlIGBvbmVfaG90ID0gVFJVRWAgYXJndW1lbnQuIApPbmUgaG90IGVuY29kaW5nIG1lYW5zIHRoYXQgd2UgZG8gbm90IHNpbXBseSBlbmNvZGUgb3VyIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBudW1lcmljYWxseSwgYXMgb3VyIG51bWVyaWMgYXNzaWdubWVudHMgY2FuIGJlIGludGVycHJldGVkIGJ5IGFsZ29yaXRobXMgYXMgaGF2aW5nIGEgcGFydGljdWxhciByYW5rIG9yIG9yZGVyLiAKSW5zdGVhZCwgYmluYXJ5IHZhcmlhYmxlcyBtYWRlIG9mIDFzIGFuZCAwcyBhcmUgdXNlZCB0byBhcmJpdHJhcmlseSBhc3NpZ24gYSBudW1lcmljIHZhbHVlIHRoYXQgaGFzIG5vIGFwcGFyZW50IG9yZGVyLgoKYGBge3J9CnNpbXBsZV9yZWMgJT4lCiAgc3RlcF9kdW1teShzdGF0ZSwgY291bnR5LCBjaXR5LCB6Y3RhLCBvbmVfaG90ID0gVFJVRSkKYGBgCgpPdXIgYGZpcHNgIHZhcmlhYmxlIGluY2x1ZGVzIGEgbnVtZXJpYyBjb2RlIGZvciBzdGF0ZSBhbmQgY291bnR5IC0gYW5kIHRoZXJlZm9yZSBpcyBlc3NlbnRpYWxseSBhIHByb3h5IGZvciBjb3VudHkuClNpbmNlIHdlIGFscmVhZHkgaGF2ZSBjb3VudHksIHdlIHdpbGwganVzdCB1c2UgaXQgYW5kIGtlZXAgdGhlIGBmaXBzYCBJRCBhcyBhbm90aGVyIElEIHZhcmlhYmxlLgoKV2UgY2FuIHJlbW92ZSB0aGUgYGZpcHNgIHZhcmlhYmxlIGZyb20gdGhlIHByZWRpY3RvcnMgdXNpbmcgYHVwZGF0ZV9yb2xlKClgIHRvIG1ha2Ugc3VyZSB0aGF0IHRoZSByb2xlIGlzIG5vIGxvbmdlciBgInByZWRpY3RvciJgLiAKV2UgY2FuIG1ha2UgdGhlIHJvbGUgYW55dGhpbmcgd2Ugd2FudCBhY3R1YWxseSwgc28gd2Ugd2lsbCBrZWVwIGl0IHNvbWV0aGluZyBpZGVudGlmaWFibGUuCgpgYGB7cn0Kc2ltcGxlX3JlYyAlPiUKICB1cGRhdGVfcm9sZSgiZmlwcyIsIG5ld19yb2xlID0gImNvdW50eSBpZCIpCmBgYAoKV2UgbWlnaHQgYWxzbyB3YW50IHRvIHJlbW92ZSB2YXJpYWJsZXMgdGhhdCBhcHBlYXIgdG8gYmUgcmVkdW5kYW50IGFuZCBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQgd2l0aCBvdGhlcnMsIGFzIHdlIGtub3cgZnJvbSBvdXIgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyB0aGF0IG1hbnkgb2Ygb3VyIHZhcmlhYmxlcyBhcmUgY29ycmVsYXRlZCB3aXRoIG9uZSBhbm90aGVyLiAKV2UgY2FuIGRvIHRoaXMgdXNpbmcgdGhlIGBzdGVwX2NvcnIoKWAgZnVuY3Rpb24uCgpXZSBkb24ndCB3YW50IHRvIHJlbW92ZSBzb21lIG9mIG91ciB2YXJpYWJsZXMsIGxpa2UgdGhlIGBDTUFRYCBhbmQgYGFvZGAgdmFyaWFibGVzLCB3ZSBjYW4gc3BlY2lmeSB0aGlzIHVzaW5nIHRoZSBgLWAgc2lnbiBiZWZvcmUgdGhlIG5hbWVzIG9mIHRoZXNlIHZhcmlhYmxlcyBsaWtlIHNvOgoKYGBge3J9CnNpbXBsZV9yZWMgJT4lCiAgc3RlcF9jb3JyKGFsbF9wcmVkaWN0b3JzKCksIC0gQ01BUSwgLSBhb2QpCmBgYAoKCkl0IGlzIGFsc28gYSBnb29kIGlkZWEgdG8gcmVtb3ZlIHZhcmlhYmxlcyB3aXRoIG5lYXItemVybyB2YXJpYW5jZSwgd2hpY2ggY2FuIGJlIGRvbmUgd2l0aCB0aGUgYHN0ZXBfbnp2KClgIGZ1bmN0aW9uLiAKClZhcmlhYmxlcyBoYXZlIGxvdyB2YXJpYW5jZSBpZiBhbGwgdGhlIHZhbHVlcyBhcmUgdmVyeSBzaW1pbGFyLCB0aGUgdmFsdWVzIGFyZSB2ZXJ5IHNwYXJzZSwgb3IgaWYgdGhleSBhcmUgaGlnaGx5IGltYmFsYW5jZWQuIEFnYWluIHdlIGRvbid0IHdhbnQgdG8gcmVtb3ZlIG91ciBgQ01BUWAgYW5kIGBhb2RgIHZhcmlhYmxlcy4KCmBgYHtyfQpzaW1wbGVfcmVjICU+JQogIHN0ZXBfbnp2KGFsbF9wcmVkaWN0b3JzKCksIC0gQ01BUSwgLSBhb2QpCmBgYAoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIGhlcmUgdG8gbGVhcm4gYWJvdXQgZXhhbXBsZXMgd2hlcmUgeW91IG1pZ2h0IGhhdmUgbmVhci16ZXJvIHZhcmlhbmNlIHZhcmlhYmxlczwvc3VtbWFyeT4KCjEpICoqU2ltaWxhciBWYWx1ZXMqKiAtIElmIHRoZSBwb3B1bGF0aW9uIGRlbnNpdHkgd2FzIG5lYXJseSB0aGUgc2FtZSBmb3IgZXZlcnkgemN0YSB0aGF0IGNvbnRhaW5lZCBhIG1vbml0b3IsIHRoZW4ga25vd2luZyB0aGUgcG9wdWxhdGlvbiBkZW5zaXR5IG5lYXIgb3VyIG1vbml0b3Igd291bGQgY29udHJpYnV0ZSBsaXR0bGUgdG8gb3VyIG1vZGVsIGluIGFzc2lzdGluZyB1cyB0byBwcmVkaWN0IG1vbml0b3IgYWlyIHBvbGx1dGlvbiB2YWx1ZXMuIAoyKSAqKlNwYXJzZSBEYXRhKiogLSBJZiBhbGwgb2YgdGhlIG1vbml0b3JzIHdlcmUgaW4gbG9jYXRpb25zIHdoZXJlIHRoZSBwb3B1bGF0aW9ucyBkaWQgbm90IGF0dGVuZCBncmFkdWF0ZSBzY2hvb2wsIHRoZW4gdGhlc2UgdmFsdWVzIHdvdWxkIG1vc3RseSBiZSB6ZXJvLCBhZ2FpbiB0aGlzIHdvdWxkIGRvIHZlcnkgbGl0dGxlIHRvIGhlbHAgdXMgZGlzdGluZ3Vpc2ggb3VyIGFpciBwb2xsdXRpb24gbW9uaXRvcnMuV2hlbiBtYW55IG9mIHRoZSB2YWx1ZXMgYXJlIHplcm8gdGhpcyBpcyBhbHNvIGNhbGxlZCBzcGFyc2UgZGF0YS4gIAozKSAqKkltYmFsYW5jZWQgRGF0YSoqIElmIG5lYXJseSBhbGwgb2YgdGhlIG1vbml0b3JzIHdlcmUgbG9jYXRlZCBpbiBvbmUgcGFydGljdWxhciBzdGF0ZSwgYW5kIGFsbCB0aGUgb3RoZXJzIG9ubHkgaGFkIG9uZSBtb25pdG9yIGVhY2gsIHRoZW4gdGhlIHJlYWwgcHJlZGljdGl2ZSB2YWx1ZSB3b3VsZCBzaW1wbHkgYmUgaW4ga25vd2luZyBpZiBhIG1vbml0b3IgaXMgbG9jYXRlZCBpbiB0aGF0IHBhcnRpY3VsYXIgc3RhdGUgb3Igbm90LiBJbiB0aGlzIGNhc2Ugd2UgZG9uJ3Qgd2FudCB0byByZW1vdmUgb3VyIHZhcmlhYmxlLCB3ZSBqdXN0IHdhbnQgdG8gc2ltcGxpZnkgaXQuCgpTZWUgdGhpcyBbYmxvZyBwb3N0XShodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS9uZWFyLXplcm8tdmFyaWFuY2UtcHJlZGljdG9ycy1zaG91bGQtd2UtcmVtb3ZlLXRoZW0vKXt0YXJnZXQ9Il9ibGFuayJ9IGFib3V0IHdoeSByZW1vdmluZyBuZWFyLXplcm8gdmFyaWFuY2UgdmFyaWFibGVzIGlzbid0IGFsd2F5cyBhIGdvb2QgaWRlYSBpZiB3ZSB0aGluayB0aGF0IGEgdmFyaWFibGUgbWlnaHQgYmUgZXNwZWNpYWxseSBpbmZvcm1hdGl2ZS4KCjwvZGV0YWlscz4KCkxldCdzIHB1dCBhbGwgdGhpcyB0b2dldGhlciBub3cuIAoKKipSZW1lbWJlcjogaXQgaXMgaW1wb3J0YW50IHRvIGFkZCB0aGUgc3RlcHMgdG8gdGhlIHJlY2lwZSBpbiBhbiBvcmRlciB0aGF0IG1ha2VzIHNlbnNlIGp1c3QgbGlrZSB3aXRoIGEgY29va2luZyByZWNpcGUuKioKCkZpcnN0LCB3ZSBhcmUgZ29pbmcgdG8gY3JlYXRlIG51bWVyaWMgdmFsdWVzIGZvciBvdXIgY2F0ZWdvcmljYWwgdmFyaWFibGVzLCB0aGVuIHdlIHdpbGwgbG9vayBhdCBjb3JyZWxhdGlvbiBhbmQgbmVhci16ZXJvIHZhcmlhbmNlLiAKQWdhaW4sIHdlIGRvIG5vdCB3YW50IHRvIHJlbW92ZSB0aGUgYENNQVFgIGFuZCBgYW9kYCB2YXJpYWJsZXMsIHNvIHdlIGNhbiBtYWtlIHN1cmUgdGhleSBhcmUga2VwdCBpbiB0aGUgbW9kZWwgYnkgZXhjbHVkaW5nIHRoZW0gZnJvbSB0aG9zZSBzdGVwcy4gCklmIHdlIHNwZWNpZmljYWxseSB3YW50ZWQgdG8gcmVtb3ZlIGEgcHJlZGljdG9yIHdlIGNvdWxkIHVzZSBgc3RlcF9ybSgpYC4KCmBgYHtyfQpzaW1wbGVfcmVjICU8PiUKICB1cGRhdGVfcm9sZSgiZmlwcyIsIG5ld19yb2xlID0gImNvdW50eSBpZCIpICU+JQogIHN0ZXBfZHVtbXkoc3RhdGUsIGNvdW50eSwgY2l0eSwgemN0YSwgb25lX2hvdCA9IFRSVUUpICU+JQogIHN0ZXBfY29ycihhbGxfcHJlZGljdG9ycygpLCAtIENNQVEsIC0gYW9kKSU+JQogIHN0ZXBfbnp2KGFsbF9wcmVkaWN0b3JzKCksIC0gQ01BUSwgLSBhb2QpCiAgCnNpbXBsZV9yZWMKYGBgCgoKCiMjIFJ1bm5pbmcgdGhlIHByZS1wcm9jZXNzaW5nCgojIyMgU3RlcCAxOiBVcGRhdGUgdGhlIHJlY2lwZSB3aXRoIHRyYWluaW5nIGRhdGEgdXNpbmcgYHByZXAoKWAKClRoZSBuZXh0IG1ham9yIGZ1bmN0aW9uIG9mIHRoZSBgcmVjaXBlc2AgcGFja2FnZSBpcyBgcHJlcCgpYC4KVGhpcyBmdW5jdGlvbiB1cGRhdGVzIHRoZSByZWNpcGUgb2JqZWN0IGJhc2VkIG9uIHRoZSB0cmFpbmluZyBkYXRhLiAKSXQgZXN0aW1hdGVzIHBhcmFtZXRlcnMgKGVzdGltYXRpbmcgdGhlIHJlcXVpcmVkIHF1YW50aXRpZXMgYW5kIHN0YXRpc3RpY3MgcmVxdWlyZWQgYnkgdGhlIHN0ZXBzIGZvciB0aGUgdmFyaWFibGVzKSBmb3IgcHJlLXByb2Nlc3NpbmcgYW5kIHVwZGF0ZXMgdGhlIHZhcmlhYmxlcyByb2xlcywgYXMgc29tZSBvZiB0aGUgcHJlZGljdG9ycyBtYXkgYmUgcmVtb3ZlZCwgdGhpcyBhbGxvd3MgdGhlIHJlY2lwZSB0byBiZSByZWFkeSB0byB1c2Ugb24gb3RoZXIgZGF0YSBzZXRzLiAKSXQgKipkb2VzIG5vdCBuZWNlc3NhcmlseSBhY3R1YWxseSBleGVjdXRlIHRoZSBwcmUtcHJvY2Vzc2luZyBpdHNlbGYqKiwgaG93ZXZlciB3ZSB3aWxsIHNwZWNpZnkgaW4gYXJndW1lbnQgZm9yIGl0IHRvIGRvIHRoaXMgc28gdGhhdCB3ZSBjYW4gdGFrZSBhIGxvb2sgYXQgdGhlIHByZS1wcm9jZXNzZWQgZGF0YS4KCgpUaGVyZSBhcmUgc29tZSBpbXBvcnRhbnQgYXJndW1lbnRzIHRvIGtub3cgYWJvdXQ6CgoxLiBgdHJhaW5pbmdgIC0geW91IG11c3Qgc3VwcGx5IGEgdHJhaW5pbmcgZGF0YSBzZXQgdG8gZXN0aW1hdGUgcGFyYW1ldGVycyBmb3IgcHJlLXByb2Nlc3Npbmcgb3BlcmF0aW9ucyAocmVjaXBlIHN0ZXBzKSAtIHRoaXMgbWF5IGFscmVhZHkgYmUgaW5jbHVkZWQgaW4geW91ciByZWNpcGUgLSBhcyBpcyB0aGUgY2FzZSBmb3IgdXMKMi4gYGZyZXNoYCAtIGlmIGBmcmVzaD1UUlVFYCwgLSB3aWxsIHJldHJhaW4gYW5kIGVzdGltYXRlIHBhcmFtZXRlcnMgZm9yIGFueSBwcmV2aW91cyBzdGVwcyB0aGF0IHdlcmUgYWxyZWFkeSBwcmVwcGVkIGlmIHlvdSBhZGQgbW9yZSBzdGVwcyB0byB0aGUgcmVjaXBlCjMuIGB2ZXJib3NlYCAtIGlmIGB2ZXJib3NlPVRSVUVgLCBzaG93cyB0aGUgcHJvZ3Jlc3MgYXMgdGhlIHN0ZXBzIGFyZSBldmFsdWF0ZWQgYW5kIHRoZSBzaXplIG9mIHRoZSBwcmUtcHJvY2Vzc2VkIHRyYWluaW5nIHNldAo0LiBgcmV0YWluYCAtIGlmIGByZXRhaW49VFJVRWAsIHRoZW4gdGhlIHByZS1wcm9jZXNzZWQgdHJhaW5pbmcgc2V0IHdpbGwgYmUgc2F2ZWQgd2l0aGluIHRoZSByZWNpcGUgKGFzIHRlbXBsYXRlKS4gVGhpcyBpcyBnb29kIGlmIHlvdSBhcmUgbGlrZWx5IHRvIGFkZCBtb3JlIHN0ZXBzIGFuZCBkbyBub3Qgd2FudCB0byByZXJ1biB0aGUgYHByZXAoKWAgb24gdGhlIHByZXZpb3VzIHN0ZXBzLiBIb3dldmVyIHRoaXMgY2FuIG1ha2UgdGhlIHJlY2lwZSBzaXplIGxhcmdlLiBUaGlzIGlzIG5lY2Vzc2FyeSBpZiB5b3Ugd2FudCB0byBhY3R1YWxseSBsb29rIGF0IHRoZSBwcmUtcHJvY2Vzc2VkIGRhdGEuCgpMZXQncyB0cnkgb3V0IHRoZSBgcHJlcCgpYCBmdW5jdGlvbjogCgpgYGB7cn0KcHJlcHBlZF9yZWMgPC0gcHJlcChzaW1wbGVfcmVjLCB2ZXJib3NlID0gVFJVRSwgcmV0YWluID0gVFJVRSApCm5hbWVzKHByZXBwZWRfcmVjKQpgYGAKClRoZXJlIGFyZSBhbHNvIGxvdHMgb2YgdXNlZnVsIHRoaW5ncyB0byBjaGVja291dCBpbiB0aGUgb3V0cHV0IG9mIGBwcmVwKClgLgpZb3UgY2FuIHNlZToKCjEuIHRoZSBgc3RlcHNgIHRoYXQgd2VyZSBydW4gIAoyLiB0aGUgb3JpZ2luYWwgdmFyaWFibGUgaW5mbyAoYHZhcl9pbmZvYCkgIAozLiB0aGUgdXBkYXRlZCB2YXJpYWJsZSBpbmZvIGFmdGVyIHByZS1wcm9jZXNzaW5nIChgdGVybV9pbmZvYCkKNC4gdGhlIG5ldyBgbGV2ZWxzYCBvZiB0aGUgdmFyaWFibGVzIAo1LiB0aGUgb3JpZ2luYWwgbGV2ZWxzIG9mIHRoZSB2YXJpYWJsZXMgKGBvcmlnX2x2bHNgKQo2LiBpbmZvIGFib3V0IHRoZSB0cmFpbmluZyBkYXRhIHNldCBzaXplIGFuZCBjb21wbGV0ZW5lc3MgKGB0cl9pbmZvYCkKCioqTm90ZSoqOiBZb3UgbWF5IHNlZSB0aGUgYHByZXAucmVjaXBlKClgIGZ1bmN0aW9uIGluIG1hdGVyaWFsIHRoYXQgeW91IHJlYWQgYWJvdXQgdGhlIGByZWNpcGVzYCBwYWNrYWdlLiBUaGlzIGlzIHJlZmVycmluZyB0byB0aGUgYHByZXAoKWAgZnVuY3Rpb24gb2YgdGhlIGByZWNpcGVzYCBwYWNrYWdlLgoKCiMjIyBTdGVwIDI6IEV4dHJhY3QgcHJlLXByb2Nlc3NlZCB0cmFpbmluZyBkYXRhIHVzaW5nIGBqdWljZSgpYAoKU2luY2Ugd2UgcmV0YWluZWQgb3VyIHByZS1wcm9jZXNzZWQgdHJhaW5pbmcgZGF0YSAoaS5lLiBgcHJlcChyZXRhaW49VFJVRSlgKSwgd2UgY2FuIHRha2UgYSBsb29rIGF0IGl0IGxpa2UgYnkgdXNpbmcgdGhlIGBqdWljZSgpYCBmdW5jdGlvbiBvZiB0aGUgYHJlY2lwZXNgIHBhY2thZ2UgbGlrZSB0aGlzOgoKYGBge3IsIGVjaG89RkFMU0UsIG91dC53aWR0aD0iNDAwcHgifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWciLCJ0cmFpbmluZ19wcmVwcm9jZXNzaW5nX3JlY2lwZXMzLnBuZyIpKQpgYGAKCkxldCdzIGp1aWNlISAKCgojIyMjIHsuc2Nyb2xsYWJsZSB9CmBgYHtyfQojIFNjcm9sbCB0aHJvdWdoIHRoZSBvdXRwdXQhCmp1aWNlZF90cmFpbiA8LSBqdWljZShwcmVwcGVkX3JlYykKZ2xpbXBzZShqdWljZWRfdHJhaW4pCmBgYAojIyMjCgoKRm9yIGVhc3kgY29tcGFyaXNvbiBzYWtlIC0gaGVyZSBpcyBvdXIgb3JpZ2luYWwgZGF0YToKCiMjIyMgey5zY3JvbGxhYmxlIH0KCmBgYHtyfQojIFNjcm9sbCB0aHJvdWdoIHRoZSBvdXRwdXQhCmdsaW1wc2UocG0pCmBgYAojIyMjCgpOb3RpY2UgaG93IHdlIG9ubHkgaGF2ZSAzNiB2YXJpYWJsZXMgbm93IGluc3RlYWQgb2YgNTAhIApUd28gb2YgdGhlc2UgYXJlIG91ciBJRCB2YXJpYWJsZXMgKGBmaXBzYCBhbmQgdGhlIGFjdHVhbCBtb25pdG9yIElEIChgaWRgKSkgYW5kIG9uZSBpcyBvdXIgb3V0Y29tZSAoYHZhbHVlYCkuIApUaHVzIHdlIG9ubHkgaGF2ZSAzMyBwcmVkaWN0b3JzIG5vdy4gCldlIGNhbiBhbHNvIHNlZSB0aGF0IHZhcmlhYmxlcyB0aGF0IHdlIG5vIGxvbmdlciBoYXZlIGFueSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMuIApWYXJpYWJsZXMgbGlrZSBgc3RhdGVgIGFyZSBnb25lIGFuZCBvbmx5IGBzdGF0ZV9DYWxpZm9ybmlhYCByZW1haW5zIGFzIGl0IHdhcyB0aGUgb25seSBzdGF0ZSBpZGVudGl0eSB0byBoYXZlIG5vbnplcm8gdmFyaWFuY2UuCldlIGNhbiBhbHNvIHNlZSB0aGF0IHRoZXJlIHdlcmUgbW9yZSBtb25pdG9ycyBsaXN0ZWQgYXMgYCJOb3QgaW4gYSBjaXR5ImAgdGhhbiBhbnkgY2l0eS4gCgpXZSBjYW4gc2VlIHRoYXQgQ2FsaWZvcm5pYSBoYWQgdGhlIGxhcmdlc3QgbnVtYmVyIG9mIG1vbml0b3JzIGNvbXBhcmVkIHRvIHRoZSBvdGhlciBzdGF0ZXMuCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpwbSAlPiUgY291bnQoc3RhdGUpIApgYGAKCgpTY3JvbGwgdGhyb3VnaCB0aGUgb3V0cHV0OgoKIyMjIyB7LnNjcm9sbGFibGUgfQoKYGBge3IsIGVjaG8gPSBGQUxTRX0KcG0gJT4lIGNvdW50KHN0YXRlKSAgJT4lCiAgcHJpbnQobiA9IDFlMykKYGBgCgojIyMjCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpwbSAlPiUgY291bnQoY2l0eSkKYGBgCgpTY3JvbGwgdGhyb3VnaCB0aGUgb3V0cHV0OgoKIyMjIyB7LnNjcm9sbGFibGUgfQoKYGBge3IsIGVjaG89RkFMU0V9CnBtICU+JSBjb3VudChjaXR5KSAlPiUKICBwcmludChuID0gMWUzKQpgYGAKCiMjIyMKCioqTm90ZSoqOiBSZWNhbGwgdGhhdCB5b3UgbXVzdCBzcGVjaWZ5IGByZXRhaW4gPSBUUlVFYCBhcmd1bWVudCBvZiB0aGUgYHByZXAoKWAgZnVuY3Rpb24gdG8gdXNlIGBqdWljZSgpYC4KCiMjIyBTdGVwIDM6IEV4dHJhY3QgcHJlLXByb2Nlc3NlZCB0ZXN0aW5nIGRhdGEgdXNpbmcgYGJha2UoKWAKCkFjY29yZGluZyB0byB0aGUgYHRpZHltb2RlbHNgIGRvY3VtZW50YXRpb246Cgo+IGBiYWtlKClgIHRha2VzIGEgdHJhaW5lZCByZWNpcGUgYW5kIGFwcGxpZXMgdGhlIG9wZXJhdGlvbnMgdG8gYSBkYXRhIHNldCB0byBjcmVhdGUgYSBkZXNpZ24gbWF0cml4LgogRm9yIGV4YW1wbGU6IGl0IGFwcGxpZXMgdGhlIGNlbnRlcmluZyB0byBuZXcgZGF0YSBzZXRzIHVzaW5nIHRoZXNlIG1lYW5zIHVzZWQgdG8gY3JlYXRlIHRoZSByZWNpcGUKClRoZXJlZm9yZSwgaWYgeW91IHdhbnRlZCB0byBsb29rIGF0IHRoZSBwcmUtcHJvY2Vzc2VkIHRlc3RpbmcgZGF0YSB5b3Ugd291bGQgdXNlIHRoZSBgYmFrZSgpYCBmdW5jdGlvbiBvZiB0aGUgYHJlY2lwZXNgIHBhY2thZ2UuCihZb3UgZ2VuZXJhbGx5IHdhbnQgdG8gbGVhdmUgeW91ciB0ZXN0aW5nIGRhdGEgYWxvbmUsIGJ1dCBpdCBpcyBnb29kIHRvIGxvb2sgZm9yIGlzc3VlcyBsaWtlIHRoZSBpbnRyb2R1Y3Rpb24gb2YgTkEgdmFsdWVzKS4KCmBgYHtyLCBlY2hvPUZBTFNFLCBvdXQud2lkdGg9IjQwMHB4In0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwidGVzdGluZ19wcmVwcm9jZXNzaW5nX3JlY2lwZXM0LnBuZyIpKQpgYGAKCkxldCdzIGJha2UhIAoKIyMjIyB7LnNjcm9sbGFibGUgfQpgYGB7cix9CiMgU2Nyb2xsIHRocm91Z2ggdGhlIG91dHB1dCEKYmFrZWRfdGVzdF9wbSA8LSByZWNpcGVzOjpiYWtlKHByZXBwZWRfcmVjLCBuZXdfZGF0YSA9IHRlc3RfcG0pCmdsaW1wc2UoYmFrZWRfdGVzdF9wbSkKYGBgCiMjIyMKCgpOb3RpY2UgdGhhdCBvdXIgYGNpdHlfTm90LmluLmEuY2l0eWAgdmFyaWFibGUgc2VlbXMgdG8gYmUgTkEgdmFsdWVzLiAKV2h5IG1pZ2h0IHRoYXQgYmU/CgpBaCEgUGVyaGFwcyBpdCBpcyBiZWNhdXNlIHNvbWUgb2Ygb3VyIGxldmVscyB3ZXJlIG5vdCBwcmV2aW91c2x5IHNlZW4gaW4gdGhlIHRyYWluaW5nIHNldCEKCkxldCdzIHRha2UgYSBsb29rIHVzaW5nIHRoZSBbc2V0IG9wZXJhdGlvbnNdKGh0dHBzOi8vd3d3LnByb2JhYmlsaXR5Y291cnNlLmNvbS9jaGFwdGVyMS8xXzJfMl9zZXRfb3BlcmF0aW9ucy5waHApe3RhcmdldD0iX2JsYW5rIn0gb2YgdGhlIGBkcGx5cmAgcGFja2FnZS4gCldlIGNhbiB0YWtlIGEgbG9vayBhdCBjaXRpZXMgdGhhdCB3ZXJlIGRpZmZlcmVudCBiZXR3ZWVuIHRoZSB0ZXN0IGFuZCB0cmFpbmluZyBzZXQuCgpgYGB7cn0KdHJhaW5jaXRpZXMgPC0gdHJhaW5fcG0gJT4lIGRpc3RpbmN0KGNpdHkpCnRlc3RjaXRpZXMgPC0gdGVzdF9wbSAlPiUgZGlzdGluY3QoY2l0eSkKCiNnZXQgdGhlIG51bWJlciBvZiBjaXRpZXMgdGhhdCB3ZXJlIGRpZmZlcmVudApkaW0oZHBseXI6OnNldGRpZmYodHJhaW5jaXRpZXMsIHRlc3RjaXRpZXMpKQoKI2dldCB0aGUgbnVtYmVyIG9mIGNpdGllcyB0aGF0IG92ZXJsYXBwZWQKZGltKGRwbHlyOjppbnRlcnNlY3QodHJhaW5jaXRpZXMsIHRlc3RjaXRpZXMpKQpgYGAKCkluZGVlZCwgdGhlcmUgYXJlIGxvdHMgb2YgZGlmZmVyZW50IGNpdGllcyBpbiBvdXIgdGVzdCBkYXRhIHRoYXQgYXJlIG5vdCBpbiBvdXIgdHJhaW5pbmcgZGF0YSEKCgpTbywgbGV0IGdvIGJhY2sgdG8gb3VyIGBwbWAgZGF0YSBzZXQgYW5kIG1vZGlmeSB0aGUgYGNpdHlgIHZhcmlhYmxlIHRvIGp1c3QgYmUgdmFsdWVzIG9mIGBpbiBhIGNpdHlgIG9yIGBub3QgaW4gYSBjaXR5YCB1c2luZyB0aGUgYGNhc2Vfd2hlbigpYCBmdW5jdGlvbiBvZiBgZHBseXJgLgpUaGlzIGZ1bmN0aW9uIGFsbG93cyB5b3UgdG8gdmVjdG9yaXplIG11bHRpcGxlIGBpZl9lbHNlKClgIHN0YXRlbWVudHMuCgpgYGB7cn0KcG0gJT4lCiAgbXV0YXRlKGNpdHkgPSBjYXNlX3doZW4oY2l0eSA9PSAiTm90IGluIGEgY2l0eSIgfiAiTm90IGluIGEgY2l0eSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2l0eSAhPSAiTm90IGluIGEgY2l0eSIgfiAiSW4gYSBjaXR5IikpCmBgYAoKQWx0ZXJuYXRpdmVseSB5b3UgY291bGQgY3JlYXRlIGEgW2N1c3RvbSBzdGVwIGZ1bmN0aW9uXShodHRwczovL3JlY2lwZXMudGlkeW1vZGVscy5vcmcvYXJ0aWNsZXMvQ3VzdG9tX1N0ZXBzLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gdG8gZG8gdGhpcyBhbmQgYWRkIHRoaXMgdG8geW91ciByZWNpcGUsIGJ1dCB0aGF0IGlzIGJleW9uZCB0aGUgc2NvcGUgb2YgdGhpcyBjYXNlIHN0dWR5LiAKCldlIHdpbGwgbmVlZCB0byByZXBlYXQgYWxsIHRoZSBzdGVwcyAoc3BsaXR0aW5nIHRoZSBkYXRhLCBwcmUtcHJvY2Vzc2luZywgZXRjKSBhcyB0aGUgbGV2ZWxzIG9mIG91ciB2YXJpYWJsZXMgaGF2ZSBub3cgY2hhbmdlZC4gCgpXaGlsZSB3ZSBhcmUgZG9pbmcgdGhpcywgd2UgbWlnaHQgYWxzbyBoYXZlIHRoaXMgaXNzdWUgZm9yIGBjb3VudHlgLiAKClRoZSBgY291bnR5YCB2YXJpYWJsZXMgYXBwZWFycyB0byBnZXQgZHJvcHBlZCBkdWUgdG8gZWl0aGVyIGNvcnJlbGF0aW9uIG9yIG5lYXIgemVybyB2YXJpYW5jZS4gCgpJdCBpcyBsaWtlbHkgZHVlIHRvIG5lYXIgemVybyB2YXJpYW5jZSBiZWNhdXNlIHRoaXMgaXMgdGhlIG1vcmUgZ3JhbnVsYXIgb2YgdGhlc2UgZ2VvZ3JhcGhpYyBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYW5kIGxpa2VseSBzcGFyc2UuCgpgYGB7cn0KcG0gJTw+JQogIG11dGF0ZShjaXR5ID0gY2FzZV93aGVuKGNpdHkgPT0gIk5vdCBpbiBhIGNpdHkiIH4gIk5vdCBpbiBhIGNpdHkiLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNpdHkgIT0gIk5vdCBpbiBhIGNpdHkiIH4gIkluIGEgY2l0eSIpKQoKc2V0LnNlZWQoMTIzNCkgIyBzYW1lIHNlZWQgYXMgYmVmb3JlCnBtX3NwbGl0IDwtcnNhbXBsZTo6aW5pdGlhbF9zcGxpdChkYXRhID0gcG0sIHByb3AgPSAyLzMpCnBtX3NwbGl0CiB0cmFpbl9wbSA8LXJzYW1wbGU6OnRyYWluaW5nKHBtX3NwbGl0KQogdGVzdF9wbSA8LXJzYW1wbGU6OnRlc3RpbmcocG1fc3BsaXQpCmBgYAoKCiMjIyMgey5yZWNhbGxfY29kZV9xdWVzdGlvbl9ibG9ja30KPGI+PHU+IFF1ZXN0aW9uIE9wcG9ydHVuaXR5IDwvdT48L2I+CgpTZWUgaWYgeW91IGNhbiBjb21lIHVwIHdpdGggdGhlIGNvZGUgZm9yIHRoZSBuZXcgcmVjaXBlLgoKIyMjIwoKKioqCgo8ZGV0YWlscz4gPHN1bW1hcnk+IENsaWNrIGhlcmUgdG8gcmV2ZWFsIHRoZSBjb2RlIGZvciB0aGUgbmV3IHJlY2lwZS4gPC9zdW1tYXJ5PgoKCmBgYHtyfQpub3ZlbF9yZWMgPC1yZWNpcGUodHJhaW5fcG0pICU+JQogICAgdXBkYXRlX3JvbGUoZXZlcnl0aGluZygpLCBuZXdfcm9sZSA9ICJwcmVkaWN0b3IiKSAlPiUKICAgIHVwZGF0ZV9yb2xlKHZhbHVlLCBuZXdfcm9sZSA9ICJvdXRjb21lIikgJT4lCiAgICB1cGRhdGVfcm9sZShpZCwgbmV3X3JvbGUgPSAiaWQgdmFyaWFibGUiKSAlPiUKICAgIHVwZGF0ZV9yb2xlKCJmaXBzIiwgbmV3X3JvbGUgPSAiY291bnR5IGlkIikgJT4lCiAgICBzdGVwX2R1bW15KHN0YXRlLCBjb3VudHksIGNpdHksIHpjdGEsIG9uZV9ob3QgPSBUUlVFKSAlPiUKICAgIHN0ZXBfY29ycihhbGxfbnVtZXJpYygpKSAlPiUKICAgIHN0ZXBfbnp2KGFsbF9udW1lcmljKCkpIApgYGAKPC9kZXRhaWxzPgoqKioKCmBgYHtyfQpub3ZlbF9yZWMKYGBgCgoKCk5vdyBsZXQncyByZXRyYWluIG91ciB0cmFpbmluZyBkYXRhIGFuZCB0cnkgYmFraW5nIG91ciB0ZXN0IGRhdGEuCgoKCiMjIyMgey5yZWNhbGxfY29kZV9xdWVzdGlvbl9ibG9ja30KPGI+PHU+IFF1ZXN0aW9uIE9wcG9ydHVuaXR5IDwvdT48L2I+CgpEbyB5b3UgcmVjYWxsIGhvdyB0byBwcmUtcHJvY2VzcyBhbmQgZXh0cmFjdCB0aGUgcHJlLXByb2Nlc3NlZCB0cmFpbmluZyBkYXRhPwoKIyMjIwoKKioqCgo8ZGV0YWlscz4gPHN1bW1hcnk+IENsaWNrIGhlcmUgdG8gcmV2ZWFsIHRoZSBhbnN3ZXIuIDwvc3VtbWFyeT4KCmBgYHtyfQpwcmVwcGVkX3JlYyA8LSBwcmVwKG5vdmVsX3JlYywgdmVyYm9zZSA9IFRSVUUsIHJldGFpbiA9IFRSVUUpCmp1aWNlZF90cmFpbiA8LSBqdWljZShwcmVwcGVkX3JlYykKYGBgCjwvZGV0YWlscz4gCioqKgoKCiMjIyMgey5zY3JvbGxhYmxlIH0KYGBge3J9CiMgU2Nyb2xsIHRocm91Z2ggdGhlIG91dHB1dCEKZ2xpbXBzZShqdWljZWRfdHJhaW4pCmBgYAoKIyMjIwoKQW5kIG5vdywgbGV0J3MgdHJ5IGJha2luZyBvdXIgdGVzdCBzZXQgdG8gc2VlIGlmIHdlIHN0aWxsIGhhdmUgYE5BYCB2YWx1ZXMuCgojIyMjIHsuc2Nyb2xsYWJsZSB9CgpgYGB7cn0KIyBTY3JvbGwgdGhyb3VnaCB0aGUgb3V0cHV0IQpiYWtlZF90ZXN0X3BtIDwtIHJlY2lwZXM6OmJha2UocHJlcHBlZF9yZWMsIG5ld19kYXRhID0gdGVzdF9wbSkKCmdsaW1wc2UoYmFrZWRfdGVzdF9wbSkKYGBgCgojIyMjCgpHcmVhdCBub3cgd2Ugbm8gbG9uZ2VyIGhhdmUgYE5BYCB2YWx1ZXMhIDopCgoqKk5vdGUqKjogaWYgeW91IHVzZSB0aGUgc2tpcCBvcHRpb24gZm9yIHNvbWUgb2YgdGhlIHByZS1wcm9jZXNzaW5nIHN0ZXBzLCBiZSBjYXJlZnVsLiAKYGp1aWNlKClgIHdpbGwgc2hvdyBhbGwgb2YgdGhlIHJlc3VsdHMgaWdub3JpbmcgYHNraXAgPSBUUlVFYC4gCmBiYWtlKClgIHdpbGwgbm90IG5lY2Vzc2FyaWx5IGNvbmR1Y3QgdGhlc2Ugc3RlcHMgb24gdGhlIG5ldyBkYXRhLgoKCiMjIFNwZWNpZnlpbmcgdGhlIG1vZGVsCgpTbyBmYXIgd2UgaGF2ZSB1c2VkIHRoZSBwYWNrYWdlcyBgcnNhbXBsZWAgdG8gc3BsaXQgdGhlIGRhdGEgYW5kIGByZWNpcGVzYCB0byBhc3NpZ24gdmFyaWFibGUgdHlwZXMsIGFuZCB0byBzcGVjaWZ5IGFuZCBwcmVwIG91ciBwcmUtcHJvY2Vzc2luZyAoYXMgd2VsbCBhcyB0byBvcHRpb25hbGx5IGV4dHJhY3QgdGhlIHByZS1wcm9jZXNzZWQgZGF0YSkuCgpXZSB3aWxsIG5vdyB1c2UgdGhlIGBwYXJzbmlwYCBwYWNrYWdlICh3aGljaCBpcyBzaW1pbGFyIHRvIHRoZSBwcmV2aW91cyBgY2FyZXRgIHBhY2thZ2UgLSBhbmQgaGVuY2Ugd2h5IGl0IGlzIG5hbWVkIGFmdGVyIHRoZSB2ZWdldGFibGUpIHRvIHNwZWNpZnkgb3VyIG1vZGVsLgoKVGhlcmUgYXJlIGZvdXIgdGhpbmdzIHdlIG5lZWQgdG8gZGVmaW5lIGFib3V0IG91ciBtb2RlbDogIAoKMS4gVGhlICoqdHlwZSoqIG9mIG1vZGVsICh1c2luZyBzcGVjaWZpYyBmdW5jdGlvbnMgaW4gcGFyc25pcCBsaWtlIGByYW5kX2ZvcmVzdCgpYCwgYGxvZ2lzdGljX3JlZygpYCBldGMuKSAgCjIuIFRoZSBwYWNrYWdlIG9yICoqZW5naW5lKiogdGhhdCB3ZSB3aWxsIHVzZSB0byBpbXBsZW1lbnQgdGhlIHR5cGUgb2YgbW9kZWwgc2VsZWN0ZWQgKHVzaW5nIHRoZSBgc2V0X2VuZ2luZSgpYCBmdW5jdGlvbikgCjMuIFRoZSAqKm1vZGUqKiBvZiBsZWFybmluZyAtIGNsYXNzaWZpY2F0aW9uIG9yIHJlZ3Jlc3Npb24gKHVzaW5nIHRoZSBgc2V0X21vZGUoKWAgZnVuY3Rpb24pIAo0LiBBbnkgKiphcmd1bWVudHMqKiBuZWNlc3NhcnkgZm9yIHRoZSBtb2RlbC9wYWNrYWdlIHNlbGVjdGVkICh1c2luZyB0aGUgYHNldF9hcmdzKClgZnVuY3Rpb24gLSAgZm9yIGV4YW1wbGUgdGhlIGBtdHJ5ID1gIGFyZ3VtZW50IGZvciByYW5kb20gZm9yZXN0IHdoaWNoIGlzIHRoZSBudW1iZXIgb2YgdmFyaWFibGVzIHRvIGJlIHVzZWQgYXMgb3B0aW9ucyBmb3Igc3BsaXR0aW5nIGF0IGVhY2ggdHJlZSBub2RlKQoKTGV0J3Mgd2FsayB0aHJvdWdoIHRoZXNlIHN0ZXBzIG9uZSBieSBvbmUuIApGb3Igb3VyIGNhc2UsIHdlIGFyZSBnb2luZyB0byBzdGFydCBvdXIgYW5hbHlzaXMgd2l0aCBhIGxpbmVhciByZWdyZXNzaW9uIGJ1dCB3ZSB3aWxsIGRlbW9uc3RyYXRlIGhvdyB3ZSBjYW4gdHJ5IGRpZmZlcmVudCBtb2RlbHMuCgpUaGUgZmlyc3Qgc3RlcCBpcyB0byBkZWZpbmUgd2hhdCB0eXBlIG9mIG1vZGVsIHdlIHdvdWxkIGxpa2UgdG8gdXNlLiAKU2VlIFtoZXJlXShodHRwczovL3d3dy50aWR5bW9kZWxzLm9yZy9maW5kL3BhcnNuaXAvKXt0YXJnZXQ9Il9ibGFuayJ9IGZvciBtb2RlbGluZyBvcHRpb25zIGluIGBwYXJzbmlwYC4KCgpgYGB7cn0KUE1fbW9kZWwgPC0gcGFyc25pcDo6bGluZWFyX3JlZygpICMgUE0gd2FzIHVzZWQgaW4gdGhlIG5hbWUgZm9yIHBhcnRpY3VsYXRlIG1hdHRlcgpQTV9tb2RlbApgYGAKCk9LLiBTbyBmYXIsIGFsbCB3ZSBoYXZlIGRlZmluZWQgaXMgdGhhdCB3ZSB3YW50IHRvIHVzZSBhIGxpbmVhciByZWdyZXNzaW9uLi4uICAKTGV0J3MgdGVsbCBgcGFyc25pcGAgbW9yZSBhYm91dCB3aGF0IHdlIHdhbnQuCgpXZSB3b3VsZCBsaWtlIHRvIHVzZSB0aGUgW29yZGluYXJ5IGxlYXN0IHNxdWFyZXNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL09yZGluYXJ5X2xlYXN0X3NxdWFyZXMpIG1ldGhvZCB0byBmaXQgb3VyIGxpbmVhciByZWdyZXNzaW9uLiAKU28gd2Ugd2lsbCB0ZWxsIGBwYXJzbmlwYCB0aGF0IHdlIHdhbnQgdG8gdXNlIHRoZSBgbG1gIHBhY2thZ2UgdG8gaW1wbGVtZW50IG91ciBsaW5lYXIgcmVncmVzc2lvbiAodGhlcmUgYXJlIG1hbnkgb3B0aW9ucyBhY3R1YWxseSBzdWNoIGFzIFtgcnN0YW5gXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcnN0YW4vdmlnbmV0dGVzL3JzdGFuLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gIFtgZ2xtbmV0YF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dsbW5ldC9pbmRleC5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9LCBbYGtlcmFzYF0oaHR0cHM6Ly9rZXJhcy5yc3R1ZGlvLmNvbS8pe3RhcmdldD0iX2JsYW5rIn0sIGFuZCBbYHNwYXJrbHlyYF0oaHR0cHM6Ly90aGVyaW5zcGFyay5jb20vc3RhcnRpbmcuaHRtbCNzdGFydGluZy1zcGFya2x5ci1oZWxsby13b3JsZCl7dGFyZ2V0PSJfYmxhbmsifSkuIFNlZSBbaGVyZV0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9saW5lYXJfcmVnLmh0bWwpIGZvciBhIGRlc2NyaXB0aW9uIG9mIHRoZSBkaWZmZXJlbmNlcyBhbmQgdXNpbmcgdGhlc2UgZGlmZmVyZW50IGVuZ2luZXMgd2l0aCBgcGFyc25pcGAuCgpXZSB3aWxsIGRvIHNvIGJ5IHVzaW5nIHRoZSBgc2V0X2VuZ2luZSgpYCBmdW5jdGlvbiBvZiB0aGUgYHBhcnNuaXBgIHBhY2thZ2UuCgpgYGB7cn0KbG1fUE1fbW9kZWwgPC0gCiAgUE1fbW9kZWwgICU+JQogIHBhcnNuaXA6OnNldF9lbmdpbmUoImxtIikKCmxtX1BNX21vZGVsCmBgYAoKSW4gc29tZSBjYXNlcyBzb21lIHBhY2thZ2VzIGNhbiBkbyBlaXRoZXIgY2xhc3NpZmljYXRpb24gb3IgcHJlZGljdGlvbiwgc28gaXQgaXMgYSBnb29kIGlkZWEgdG8gc3BlY2lmeSB3aGljaCBtb2RlIHlvdSBpbnRlbmQgdG8gcGVyZm9ybS4gCkhlcmUsIHdlIGFpbSB0byBwcmVkaWN0IHRoZSBhaXIgcG9sbHV0aW9uLiAKWW91IGNhbiBkbyB0aGlzIHdpdGggdGhlIGBzZXRfbW9kZSgpYCBmdW5jdGlvbiBvZiB0aGUgYHBhcnNuaXBgIHBhY2thZ2UsIGJ5IHVzaW5nIGVpdGhlciBgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIilgIG9yIGBzZXRfbW9kZSgicmVncmVzc2lvbiIpYC4KCmBgYHtyfQpsbV9QTV9tb2RlbCA8LSAKICBQTV9tb2RlbCAgJT4lCiAgcGFyc25pcDo6c2V0X2VuZ2luZSgibG0iKSAlPiUKICBzZXRfbW9kZSgicmVncmVzc2lvbiIpCgpsbV9QTV9tb2RlbApgYGAKCiMjIEZpdHRpbmcgdGhlIG1vZGVsCgpXZSBjYW4gIHVzZSB0aGUgYHBhcnNuaXBgIHBhY2thZ2Ugd2l0aCBhIG5ld2VyIHBhY2thZ2UgY2FsbGVkIGB3b3JrZmxvd3NgIHRvIGZpdCBvdXIgbW9kZWwuIAoKVGhlIGB3b3JrZmxvd3NgIHBhY2thZ2UgYWxsb3dzIHVzIHRvIGtlZXAgdHJhY2sgb2YgYm90aCBvdXIgcHJlLXByb2Nlc3Npbmcgc3RlcHMgYW5kIG91ciBtb2RlbCBzcGVjaWZpY2F0aW9uLiBJdCBhbHNvIGFsbG93cyB1cyB0byBpbXBsZW1lbnQgZmFuY2llciBvcHRpbWl6YXRpb25zIGluIGFuIGF1dG9tYXRlZCB3YXkgYW5kIGl0IGNhbiBhbHNvIGhhbmRsZSBwb3N0LXByb2Nlc3Npbmcgb3BlcmF0aW9ucy4gCgoKV2UgYmVnaW4gYnkgY3JlYXRpbmcgYSB3b3JrZmxvdyB1c2luZyB0aGUgYHdvcmtmbG93KClgIGZ1bmN0aW9uIGluIHRoZSBgd29ya2Zsb3dzYCBwYWNrYWdlLiAKCk5leHQsIHdlIHVzZSBgYWRkX3JlY2lwZSgpYCAob3VyIHByZS1wcm9jZXNzaW5nIHNwZWNpZmljYXRpb25zKSBhbmQgd2UgYWRkIG91ciBtb2RlbCB3aXRoIHRoZSBgYWRkX21vZGVsKClgIGZ1bmN0aW9uIC0tIGJvdGggZnVuY3Rpb25zIGZyb20gdGhlIGB3b3JrZmxvd3NgIHBhY2thZ2UuCgoqKk5vdGUqKjogV2UgZG8gbm90IG5lZWQgdG8gYWN0dWFsbHkgYHByZXAoKWAgb3VyIHJlY2lwZSBiZWZvcmUgdXNpbmcgd29ya2Zsb3dzIQoKSWYgeW91IHJlY2FsbCBgbm92ZWxfcmVjYCBpcyB0aGUgcmVjaXBlIHdlIHByZXZpb3VzbHkgY3JlYXRlZCB3aXRoIHRoZSBgcmVjaXBlc2AgcGFja2FnZSBhbmQgYGxtX1BNX21vZGVsYCB3YXMgY3JlYXRlZCB3aGVuIHdlIHNwZWNpZmllZCBvdXIgbW9kZWwgd2l0aCB0aGUgYHBhcnNuaXBgIHBhY2thZ2UuCkhlcmUsIHdlIGNvbWJpbmUgZXZlcnl0aGluZyB0b2dldGhlciBpbnRvIGEgYHdvcmtmbG93KClgLiAKCmBgYHtyfQpQTV93ZmxvdyA8LXdvcmtmbG93czo6d29ya2Zsb3coKSAlPiUKICAgICAgICAgICB3b3JrZmxvd3M6OmFkZF9yZWNpcGUobm92ZWxfcmVjKSAlPiUKICAgICAgICAgICB3b3JrZmxvd3M6OmFkZF9tb2RlbChsbV9QTV9tb2RlbCkKUE1fd2Zsb3cKYGBgCgpBaCwgbmljZS4gCk5vdGljZSBob3cgaXQgdGVsbHMgdXMgYWJvdXQgYm90aCBvdXIgcHJlLXByb2Nlc3Npbmcgc3RlcHMgYW5kIG91ciBtb2RlbCBzcGVjaWZpY2F0aW9ucy4KCk5leHQsIHdlICJwcmVwYXJlIHRoZSByZWNpcGUiIChvciBlc3RpbWF0ZSB0aGUgcGFyYW1ldGVycykgYW5kIGZpdCB0aGUgbW9kZWwgdG8gb3VyIHRyYWluaW5nIGRhdGEgYWxsIGF0IG9uY2UuIApQcmludGluZyB0aGUgb3V0cHV0LCB3ZSBjYW4gc2VlIHRoZSBjb2VmZmljaWVudHMgb2YgdGhlIG1vZGVsLgoKYGBge3J9ClBNX3dmbG93X2ZpdCA8LSBwYXJzbmlwOjpmaXQoUE1fd2Zsb3csIGRhdGEgPSB0cmFpbl9wbSkKUE1fd2Zsb3dfZml0CmBgYAoKCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayBoZXJlIHRvIHNlZSB0aGUgc3RlcHMgdGhhdCB0aGUgYHdvcmtmbG93c2AgcGFja2FnZSBwZXJmb3JtcyB0aGF0IHVzZWQgdG8gYmUgcmVxdWlyZWQgPC9zdW1tYXJ5PgoKQVZPQ0FETzp0aGlzIHNlY3Rpb24gZmVlbHMgbGlrZSBpdCBzaG91bGQgZWl0aGVyIGhhdmUgbW9yZSBleHBsYW5hdGlvbiBvciBqdXN0IGRlbGV0ZWQuIFRvIG1lIGl0IGZlZWxzIGNvbmZ1c2luZyBiZWNhdXNlIGl0IGZlZWxzIGxpa2Ugd2UgYXJlIHNob3dpbmcgYSBjb21wbGV0ZWx5IGFsdGVybmF0aXZlIHBhdGggd2l0aG91dCBhbiBleHBsYW5hdGlvbiB3aHkgeW91IG1pZ2h0IHdhbnQgdG8gZG8/IG9yIHdoZW4geW91IHdvdWxkIHdhbnQgdG8gZG8gdGhpcz8gCgphdm9jYWRvIHJlc3BvbnNlLXdoYXQgZG8geW91IHRoaW5rIG9mIGhvdyBJIHRpdGxlZCB0aGlzIHNlY3Rpb24gLSBpZiB5b3Ugc3RpbGwgZG9uJ3QgbGlrZSBpdCBmZWVsIGZyZWUgdG8gZGVsZXRlCgpQcmV2aW91c2x5IHRoZSBwcm9jZXNzZWQgdHJhaW5pbmcgZGF0YSAoYGp1aWNlZF90cmFpbmApIGFzIG9wcG9zZWQgdG8gdGhlIHJhdyB0cmFpbmluZyBkYXRhIHdvdWxkIGJlIHJlcXVpcmVkIHRvIGZpdCB0aGUgbW9kZWwuCgpJbiB0aGlzIGNhc2UsIHdlIHdvdWxkIGFjdHVhbGx5IGFsc28gbmVlZGVkIHRvIHdyaXRlIHlvdXIgbW9kZWwgYWdhaW4hIApSZWNhbGwgdGhhdCBgaWRgIGFuZCBgZmlwc2AgYXJlIElEIHZhcmlhYmxlcyBhbmQgdGhhdCBgdmFsdWVzYCBpcyBvdXIgb3V0Y29tZSBvZiBpbnRlcmVzdCAodGhlIGFpciBwb2xsdXRpb24gbWVhc3VyZSBhdCBlYWNoIG1vbml0b3IpLiBJdCBpcyBuaWNlIHRoYXQgYHdvcmtmbG93c2Aga2VlcHMgdHJhY2sgb2YgdGhpcyEKCmBgYHtyfQpqdWljZWRfdHJhaW5fcmVhZHkgPC0ganVpY2VkX3RyYWluICU+JSAKICBzZWxlY3QoLWlkLCAtZmlwcykKClBNX2ZpdCA8LSBsbV9QTV9tb2RlbCAlPiUgCiAgcGFyc25pcDo6Zml0KHZhbHVlIH4uLCBkYXRhID0ganVpY2VkX3RyYWluX3JlYWR5KQpgYGAKCjwvZGV0YWlscz4KCiMjIEFzc2Vzc2luZyB0aGUgbW9kZWwgZml0CgpBZnRlciB3ZSBmaXQgb3VyIG1vZGVsLCB3ZSBjYW4gdXNlIHRoZSBgYnJvb21gIHBhY2thZ2UgdG8gbG9vayBhdCB0aGUgb3V0cHV0IGZyb20gdGhlIGZpdHRlZCBtb2RlbCBpbiBhbiBlYXN5L3RpZHkuICAgCgpUaGUgYHRpZHkoKWAgZnVuY3Rpb24gcmV0dXJucyBhIHRpZHkgZGF0YSBmcmFtZSB3aXRoIGNvZWZmaWNpZW50cyBmcm9tIHRoZSBtb2RlbCAob25lIHJvdyBwZXIgY29lZmZpY2llbnQpLgoKTWFueSBvdGhlciBgYnJvb21gIGZ1bmN0aW9ucyBjdXJyZW50bHkgb25seSB3b3JrIHdpdGggYHBhcnNuaXBgIG9iamVjdHMsIG5vdCByYXcgYHdvcmtmbG93c2Agb2JqZWN0cy4gCgpIb3dldmVyLCB3ZSBjYW4gdXNlIHRoZSBgdGlkeWAgZnVuY3Rpb24gaWYgd2UgZmlyc3QgdXNlIHRoZSBgcHVsbF93b3JrZmxvd19maXQoKWAgZnVuY3Rpb24uCgpgYGB7cn0Kd2Zsb3dvdXRwdXQgPC0gUE1fd2Zsb3dfZml0ICU+JSAKICBwdWxsX3dvcmtmbG93X2ZpdCgpICU+JSAKICBicm9vbTo6dGlkeSgpIApgYGAKCgpgYGB7cn0Kd2Zsb3dvdXRwdXQKYGBgCgpXZSBoYXZlIGZpdCBvdXIgbW9kZWwgb24gb3VyIHRyYWluaW5nIGRhdGEsIHdoaWNoIG1lYW5zIHdlIGhhdmUgY3JlYXRlZCBhIG1vZGVsIHRvIHByZWRpY3QgdmFsdWVzIG9mIGFpciBwb2xsdXRpb24gYmFzZWQgb24gdGhlIHByZWRpY3RvcnMgdGhhdCB3ZSBoYXZlIGluY2x1ZGVkLiBZYXkhCgpPbmUgbGFzdCB0aGluZyBiZWZvcmUgd2UgbGVhdmUgdGhpcyBzZWN0aW9uLiAKV2Ugb2Z0ZW4gYXJlIGludGVyZXN0ZWQgaW4gZ2V0dGluZyBhIHNlbnNlIG9mIHdoaWNoIHZhcmlhYmxlcyBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IGluIG91ciBtb2RlbC4gCldlIGNhbiBleHBsb3JlIHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlIHVzaW5nIHRoZSBgdmlwKClgIGZ1bmN0aW9uIG9mIHRoZSBgdmlwYCBwYWNrYWdlLiAKVGhpcyBmdW5jdGlvbiBjcmVhdGUgYSBiYXIgcGxvdCBvZiB2YXJpYWJsZSBpbXBvcnRhbmNlIHNjb3JlcyBmb3IgZWFjaCBwcmVkaWN0b3IgdmFyaWFibGUgKG9yIGZlYXR1cmUpIGluIGEgbW9kZWwuIApUaGUgYmFyIHBsb3QgaXMgb3JkZXJlZCBieSBpbXBvcnRhbmNlIChoaWdoZXN0IHRvIHNtYWxsZXN0KS4gCgoKTm90aWNlIGFnYWluIHRoYXQgd2UgbmVlZCB0byB1c2UgdGhlIGBwdWxsX3dvcmtmbG93X2ZpdCgpYCBmdW5jdGlvbi4KCkxldCdzIHRha2UgYSBsb29rIGF0IHRoZSB0b3AgMTAgY29udHJpYnV0aW5nIHZhcmlhYmxlczoKCmBgYHtyfQpQTV93Zmxvd19maXQgJT4lIAogIHB1bGxfd29ya2Zsb3dfZml0KCkgJT4lIAogIHZpcChudW1fZmVhdHVyZXMgPSAxMCkKYGBgCgpUaGUgc3RhdGUgaW4gd2hpY2ggdGhlIG1vbml0b3Igd2FzIGxvY2F0ZWQgYW5kIHRoZSBDTUFRIG1vZGVsIGFuZCB0aGUgYW9kIHNhdGVsbGl0ZSBpbmZvcm1hdGlvbiBhcHBlYXIgdG8gYmUgdGhlIG1vc3QgaW1wb3J0YW50IGZvciBwcmVkaWN0aW5nIHRoZSBhaXIgcG9sbHV0aW9uIGF0IGEgZ2l2ZW4gbW9uaXRvci4KCiMjIE1vZGVsIHBlcmZvcm1hbmNlCgpJbiB0aGlzIG5leHQgc2VjdGlvbiwgb3VyIGdvYWwgaXMgdG8gYXNzZXNzIHRoZSBvdmVyYWxsIG1vZGVsIHBlcmZvcm1hbmNlLiAKVGhlIHdheSB3ZSBkbyB0aGlzIGlzIHRvIGNvbXBhcmUgdGhlIHNpbWlsYXJpdHkgYmV0d2VlbiB0aGUgcHJlZGljdGVkIGVzdGltYXRlcyBvZiB0aGUgb3V0Y29tZSB2YXJpYWJsZSBwcm9kdWNlZCBieSB0aGUgbW9kZWwgYW5kIHRoZSB0cnVlIG91dGNvbWUgdmFyaWFibGUgdmFsdWVzLiAKCklmIHlvdSByZWNhbGwgdGhlIFtXaGF0IGlzIG1hY2hpbmUgbGVhcm5pbmc/XSgjd2hhdGlzbWwpIHNlY3Rpb24sIHdlIHNob3dlZCBob3cgdG8gdGhpbmsgYWJvdXQgbWFjaGluZSBsZWFybmluZyAoTUwpIGFzIGFuIG9wdGltaXphdGlvbiBwcm9ibGVtIHRoYXQgdHJpZXMgdG8gbWluaW1pemUgdGhlIGRpc3RhbmNlIGJldHdlZW4gb3VyIHByZWRpY3RlZCBvdXRjb21lICRcaGF0e1l9ID0gZihYKSQgYW5kIGFjdHVhbCBvdXRjb21lICRZJCB1c2luZyBvdXIgZmVhdHVyZXMgKG9yIHByZWRpY3RvciB2YXJpYWJsZXMpICRYJCBhcyBpbnB1dCB0byBhIGZ1bmN0aW9uICRmJCB0aGF0IHdlIHdhbnQgdG8gZXN0aW1hdGUuIAoKJCRkKFkgLSBcaGF0e1l9KSQkCgpBcyBvdXIgZ29hbCBpbiB0aGlzIHNlY3Rpb24gaXMgdG8gYXNzZXNzIG92ZXJhbGwgbW9kZWwgcGVyZm9ybWFuY2UsIHdlIHdpbGwgbm93IHRhbGsgYWJvdXQgZGlmZmVyZW50IGRpc3RhbmNlIG1ldHJpY3MgdGhhdCB5b3UgY2FuIHVzZS4gCgpGaXJzdCwgbGV0J3MgcHVsbCBvdXQgb3VyIHByZWRpY3RlZCBvdXRjb21lIHZhbHVlcyAkXGhhdHtZfSA9IGYoWCkkIGZyb20gdGhlIG1vZGVscyB3ZSBmaXQgKHVzaW5nIGRpZmZlcmVudCBhcHByb2FjaGVzKS4gCgoKYGBge3J9CndmX2ZpdCA8LSBQTV93Zmxvd19maXQgJT4lIAogIHB1bGxfd29ya2Zsb3dfZml0KCkKCndmX2ZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKHdmX2ZpdFtbImZpdCJdXSkKaGVhZCh3Zl9maXR0ZWRfdmFsdWVzKQpgYGAKCkFsdGVybmF0aXZlbHksIHdlIGNhbiBnZXQgdGhlIGZpdHRlZCB2YWx1ZXMgdXNpbmcgdGhlIGBhdWdtZW50KClgIGZ1bmN0aW9uIG9mIHRoZSBgYnJvb21gIHBhY2thZ2UgdXNpbmcgdGhlIG91dHB1dCBmcm9tIGB3b3JrZmxvd3NgOiAKCmBgYHtyfQp3Zl9maXR0ZWRfdmFsdWVzIDwtIAogIGJyb29tOjphdWdtZW50KHdmX2ZpdFtbImZpdCJdXSwgZGF0YSA9IGp1aWNlZF90cmFpbikgJT4lIAogIHNlbGVjdCh2YWx1ZSwgLmZpdHRlZDouc3RkLnJlc2lkKQoKaGVhZCh3Zl9maXR0ZWRfdmFsdWVzKQoKYGBgCgpOb3RlIHRoYXQgYmVjdWFzZSB3ZSB1c2UgdGhlIGFjdHVhbCB3b3JrZmxvdyBoZXJlLCB3ZSBjYW4gKGFuZCBhY3R1YWxseSBuZWVkIHRvKSB1c2UgdGhlIHJhdyBkYXRhIGluc3RlYWQgb2YgdGhlIHByZS1wcm9jZXNzZWQgZGF0YS4KCmBgYHtyfQp2YWx1ZXNfcHJlZF90cmFpbiA8LSAKICBwcmVkaWN0KFBNX3dmbG93X2ZpdCwgdHJhaW5fcG0pICU+JSAKICBiaW5kX2NvbHModHJhaW5fcG0gJT4lIHNlbGVjdCh2YWx1ZSwgZmlwcywgY291bnR5LCBpZCkpIAoKdmFsdWVzX3ByZWRfdHJhaW4KCmBgYAoKIyMjIFZpc3VhbGl6aW5nIG1vZGVsIHBlcmZvcm1hbmNlCgpOb3csIHdlIGNhbiBjb21wYXJlIHRoZSBwcmVkaWN0ZWQgb3V0Y29tZSB2YWx1ZXMgKG9yIGZpdHRlZCB2YWx1ZXMpICRcaGF0e1l9JCB0byB0aGUgYWN0dWFsIG91dGNvbWUgdmFsdWVzICRZJCB0aGF0IHdlIG9ic2VydmVkOiAKCmBgYHtyfQp3Zl9maXR0ZWRfdmFsdWVzICU+JSAKICBnZ3Bsb3QoYWVzKHggPSAgdmFsdWUsIHkgPSAuZml0dGVkKSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICB4bGFiKCJhY3R1YWwgb3V0Y29tZSB2YWx1ZXMiKSArIAogIHlsYWIoInByZWRpY3RlZCBvdXRjb21lIHZhbHVlcyIpCmBgYAoKT0ssIHNvIG91ciByYW5nZSBvZiB0aGUgcHJlZGljdGVkIG91dGNvbWUgdmFsdWVzIGFwcGVhcnMgdG8gYmUgc21hbGxlciB0aGFuIHRoZSByZWFsIHZhbHVlcy4gCldlIGNvdWxkIHByb2JhYmx5IGRvIGEgYml0IGJldHRlci4KCiMjIyBRdWFudGlmeWluZyBtb2RlbCBwZXJmb3JtYW5jZSAKCk5leHQsIGxldCdzIHVzZSBkaWZmZXJlbnQgZGlzdGFuY2UgZnVuY3Rpb25zICRkKFxjZG90KSQgdG8gYXNzZXNzIGhvdyBmYXIgb2ZmIG91ciBwcmVkaWN0ZWQgb3V0Y29tZSAkXGhhdHtZfSA9IGYoWCkkIGFuZCBhY3R1YWwgb3V0Y29tZSAkWSQgdmFsdWVzIGFyZSBmcm9tIGVhY2ggb3RoZXI6IAoKJCRkKFkgLSBcaGF0e1l9KSQkCgpBcyBtZW50aW9uZWQsIHRoZXJlIGFyZSBlbnRpcmUgc2Nob2xhcmx5IGZpZWxkcyBvZiByZXNlYXJjaCBkZWRpY2F0ZWQgdG8gaWRlbnRpZnlpbmcgZGlmZmVyZW50IGRpc3RhbmNlIG1ldHJpY3MgJGQoXGNkb3QpJCBmb3IgbWFjaGluZSBsZWFybmluZyBhcHBsaWNhdGlvbnMuIApIb3dldmVyLCB3aGVuIHBlcmZvcm1pbmcgcHJlZGljdGlvbiB3aXRoIGEgY29udGludW91cyBvdXRjb21lICRZJCwgYSBmZXcgb2YgdGhlIG1vc3RseSBjb21tb25seSB1c2VkIGRpc3RhbmNlIG1ldHJpY3MgYXJlOiAKCjEuIG1lYW4gYWJzb2x1dGUgZXJyb3IgKGBtYWVgKSAgCgokJE1BRSA9IFxmcmFje1xzdW1fe2k9MX1ee259eyh8XGhhdHt5X3R9LSB5X3R8KX1eMn17bn0kJAoKCjIuIFIgc3F1YXJlZCBlcnJvciAoYHJzcWApIC0tIHRoaXMgaXMgYWxzbyBrbm93biBhcyB0aGUgY29lZmZpY2llbnQgb2YgZGV0ZXJtaW5hdGlvbiB3aGljaCBpcyB0aGUgc3F1YXJlZCBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRydXRoIGFuZCBlc3RpbWF0ZQoKVGhpcyBpcyBjYWxjdWxhdGVkIGFuZCAxIG1pbnVzIHRoZSBmcmFjdGlvbiBvZiB0aGUgcmVzaWR1YWwgc3VtIG9mIHNxdWFyZXMgKCRTU19yZXMkKSBieSB0aGUgdG90YWwgc3VtIG9mIHNxdWFyZXMgKCRTU190b3QkKQoKCiQkUlNRID0gUl4yID0gMSAtIFxmcmFje1NTcmVzfXtTU3RvdH0kJAoKJCRTU197dG90fSA9IFxzdW1fe2k9MX1ee259eyh5X2ktIFxiYXJ7eX0pfV4yJCQKVGhlIHRvdGFsIHN1bSBvZiBzcXVhcmVzIGlzIHByb3BvcnRpb25hbCB0byB0aGUgdmFyaWFuY2Ugb2YgdGhlIGRhdGEuIEl0IGlzIGNhbGN1bGF0ZWQgYXMgdGhlIHN1bSBvZiBlYWNoICB0cnVlIHZhbHVlIGZyb20gdGhlIG1lYW4gdHJ1ZSB2YWx1ZSAoJFxiYXJ7eX0kKS4KCiQkU1Nfe3Jlc30gPSBcc3VtX3tpPTF9XntufXsoeV9pLSBcaGF0e3lfaX0pfV4yJCQKClRoZSBzdW0gb2Ygc3F1YXJlcyBvZiByZXNpZHVhbHMgaXMgY2FsY3VsYXRlZCBhcyB0aGUgc3VtIG9mIGVhY2ggcHJlZGljdGVkIHZhbHVlICgkXGhhdHt5X2l9JCBvciBzb21ldGltZXMgJGZfaSQpIGZyb20gdGhlIHRydWUgdmFsdWUgKCR5X2kkKS4gCgoKMy4gW3Jvb3QgbWVhbiBzcXVhcmVkIGVycm9yXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Sb290LW1lYW4tc3F1YXJlX2RldmlhdGlvbil7dGFyZ2V0PSJfYmxhbmsifSAoYHJtc2VgKSAgIAoKJCRSTVNFID0gXHNxcnR7XGZyYWN7XHN1bV97aT0xfV57bn17KFxoYXR7eV90fS0geV90KX1eMn17bn19JCQKCgoKCk9uZSB3YXkgdG8gY2FsY3VsYXRlIHRoZXNlIG1ldHJpY3Mgd2l0aGluIHRoZSBgdGlkeW1vZGVsc2AgZnJhbWV3b3JrIGlzIHRvIHVzZSB0aGUgYHlhcmRzdGlja2AgcGFja2FnZSB1c2luZyB0aGUgYG1ldHJpY3MoKWAgZnVuY3Rpb24uIAoKYGBge3J9CnlhcmRzdGljazo6bWV0cmljcyh3Zl9maXR0ZWRfdmFsdWVzLCAKICAgICAgICAgICAgICAgICAgIHRydXRoID0gdmFsdWUsIGVzdGltYXRlID0gLmZpdHRlZCkKYGBgCgpBbHRlcm5hdGl2ZWx5IGlmIHlvdSBvbmx5IHdhbnRlZCBvbmUgbWV0cmljIHlvdSBjb3VsZCB1c2UgdGhlIGBtYWUoKWAsIGByc3EoKWAsIG9yIGBybXNlKClgIGZ1bmN0aW9ucywgcmVzcGVjdGl2ZWx5LiAKCmBgYHtyfQp5YXJkc3RpY2s6Om1hZSh3Zl9maXR0ZWRfdmFsdWVzLCAKICAgICAgICAgICAgICAgdHJ1dGggPSB2YWx1ZSwgZXN0aW1hdGUgPSAuZml0dGVkKQpgYGAKCiMjIENyb3NzIHZhbGlkYXRpb24KClVudGlsIG5vdyB3ZSBoYXZlIHVzZWQgZXZlcnl0aGluZyBpbiBvdXIgInRyYWluaW5nIiBkYXRhc2V0IChhbmQgaGF2ZSBub3QgdG91Y2hlZCB0aGUgInRlc3RpbmciIGRhdGFzZXQpIGZyb20gdGhlIGByc2FtcGxlYCBwYWNrYWdlIHRvIGJ1aWxkIG91ciBtYWNoaW5lIGxlYXJuaW5nIChNTCkgbW9kZWwgJFxoYXR7WX0gPSBmKFgpJCAob3IgdG8gZXN0aW1hdGUgJGYkIHVzaW5nIHRoZSBmZWF0dXJlcyBvciBwcmVkaWN0b3IgdmFyaWFibGUgJFgkKS4gCgpIZXJlLCB3ZSB0YWtlIG1vdmUgdGhpcyBiZXlvbmQgdGhlIHNpbXBsZSBzcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGEgc2V0cy4gCldlIHdpbGwgYWdhaW4gdXNlIHRoZSBgcnNhbXBsZWAgcGFja2FnZSBhZ2FpbiBpbiBvcmRlciB0byBmdXJ0aGVyIGltcGxlbWVudCB3aGF0IGFyZSBjYWxsZWQgW2Nyb3NzIHZhbGlkYXRpb25dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0Nyb3NzLXZhbGlkYXRpb25fKHN0YXRpc3RpY3MpKXt0YXJnZXQ9Il9ibGFuayJ9IHRlY2huaXF1ZXMuIFRoaXMgaXMgYWxzbyBjYWxsZWQgKipyZS1zYW1wbGluZyoqIG9yICoqcmVwYXJ0aW9uaW5nKiouICAKCioqTm90ZSoqOiB3ZSBhcmUgbm90IGFjdHVhbGx5IGdldHRpbmcgbmV3IHNhbXBsZXMgZnJvbSB0aGUgdW5kZXJseWluZyBkaXN0cmlidXRpb24gc28gdGhlIHRlcm0gcmUtc2FtcGxpbmcgaXMgYSBiaXQgb2YgYSBtaXNub21lci4KCltDcm9zcyB2YWxpZGF0aW9uXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Dcm9zcy12YWxpZGF0aW9uXyhzdGF0aXN0aWNzKSl7dGFyZ2V0PSJfYmxhbmsifSBzcGxpdHMgb3VyIHRyYWluaW5nIGRhdGEgaW50byBtdWx0aXBsZSB0cmFpbmluZyBkYXRhIHNldHMgdG8gYWxsb3cgZm9yIGEgZGVlcGVyIGFzc2Vzc21lbnQgb2YgdGhlIGFjY3VyYWN5IG9mIHRoZSBtb2RlbC4KCkhlcmUgaXMgYSB2aXN1YWxpemF0aW9uIG9mIHRoZSBjb25jZXB0IGZvciBjcm9zcyB2YWxpZGF0aW9uL3Jlc2FtcGxpbmcvcmVwYXJ0aXRpb25pbmcgZnJvbSBbTWF4IEt1aG5dKGh0dHBzOi8vcmVzb3VyY2VzLnJzdHVkaW8uY29tL2F1dGhvcnMvbWF4LWt1aG4pe3RhcmdldD0iX2JsYW5rIn06CgpgYGB7ciwgZWNobz1GQUxTRSwgb3V0LndpZHRoPSI4MDBweCJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsInJlc2FtcGxpbmcucG5nIikpCmBgYAoKVGVjaG5pY2FsbHkgY3JlYXRpbmcgb3VyIHRlc3RpbmcgYW5kIHRyYWluaW5nIHNldCBvdXQgb2Ygb3VyIG9yaWdpbmFsIHRyYWluaW5nIGRhdGEgaXMgc29tZXRpbWVzIGNvbnNpZGVyZWQgYSBmb3JtIG9mIFtjcm9zcyB2YWxpZGF0aW9uXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Dcm9zcy12YWxpZGF0aW9uXyhzdGF0aXN0aWNzKSl7dGFyZ2V0PSJfYmxhbmsifSwgY2FsbGVkIHRoZSBob2xkb3V0IG1ldGhvZC4gClRoZSByZWFzb24gd2UgZG8gdGhpcyBpdCBzbyB3ZSBjYW4gZ2V0IGEgYmV0dGVyIHNlbnNlIG9mIHRoZSBhY2N1cmFjeSBvZiBvdXIgbW9kZWwgdXNpbmcgZGF0YSB0aGF0IHdlIGRpZCBub3QgdHJhaW4gaXQgb24uIAoKSG93ZXZlciwgd2UgY2FuIGFjdHVhbGx5IGRvIGEgYmV0dGVyIGpvYiBvZiBvcHRpbWl6aW5nIG91ciBtb2RlbCBmb3IgYWNjdXJhY3kgaWYgd2UgYWxzbyBwZXJmb3JtIGFub3RoZXIgdHlwZSBvZiBbY3Jvc3MgdmFsaWRhdGlvbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQ3Jvc3MtdmFsaWRhdGlvbl8oc3RhdGlzdGljcykpe3RhcmdldD0iX2JsYW5rIn0gb24gdGhlIG5ld2x5IGRlZmluZWQgdHJhaW5pbmcgc2V0IHRoYXQgd2UganVzdCBjcmVhdGVkLiAKVGhlcmUgYXJlIG1hbnkgW2Nyb3NzIHZhbGlkYXRpb25dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0Nyb3NzLXZhbGlkYXRpb25fKHN0YXRpc3RpY3MpKXt0YXJnZXQ9Il9ibGFuayJ9IG1ldGhvZHMgYW5kIG1vc3QgY2FuIGJlIGVhc2lseSBpbXBsZW1lbnRlZCB1c2luZyBgcnNhbXBsZWAgcGFja2FnZS4gCkhlcmUsIHdlIHdpbGwgdXNlIGEgdmVyeSBwb3B1bGFyIG1ldGhvZCBjYWxsZWQgZWl0aGVyIFtrLWZvbGQgb3Igdi1mb2xkIGNyb3NzIHZhbGlkYXRpb25dKGh0dHBzOi8vbWFjaGluZWxlYXJuaW5nbWFzdGVyeS5jb20vay1mb2xkLWNyb3NzLXZhbGlkYXRpb24vKXt0YXJnZXQ9Il9ibGFuayJ9LiAKClRoaXMgbWV0aG9kIGludm9sdmVzIGVzc2VudGlhbGx5IHByZWZvcm1pbmcgdGhlIGhvbGQgb3V0IG1ldGhvZCBpdGVyYXRpdmVseSB3aXRoIHRoZSB0cmFpbmluZyBkYXRhLiAKCkZpcnN0LCB0aGUgdHJhaW5pbmcgc2V0IGlzIGRpdmlkZWQgaW50byAkdiQgKG9yIG9mdGVuIGNhbGxlZCBjYWxsZWQgJGskKSBlcXVhbGx5IHNpemVkIHNtYWxsZXIgcGllY2VzLiAKCk5leHQsIHRoZSBtb2RlbCBpcyB0cmFpbmVkIG9uIHRoZSBtb2RlbCBvbiAkdiQtMSBzdWJzZXRzIG9mIHRoZSBkYXRhIGl0ZXJhdGl2ZWx5IChyZW1vdmluZyBhIGRpZmZlcmVudCAkdiQgdW50aWwgYWxsIHBvc3NpYmxlICR2JC0xIHNldHMgaGF2ZSBiZWVuIGV2YWx1YXRlZCkgdG8gZ2V0IGEgc2Vuc2Ugb2YgdGhlIHBlcmZvcm1hbmNlIG9mIHRoZSBtb2RlbC4gClRoaXMgaXMgcmVhbGx5IHVzZWZ1bCBmb3IgZmluZSB0dW5pbmcgc3BlY2lmaWMgYXNwZWN0cyBvZiB0aGUgbW9kZWwgaW4gYSBwcm9jZXNzIGNhbGxlZCBtb2RlbCB0dW5pbmcsIHdoaWNoIHdlIHdpbGwgbGVhcm4gYWJvdXQgaW4gdGhlIG5leHQgc2VjdGlvbi4gCgpIZXJlIGlzIGEgdmlzdWFsaXphdGlvbiBvZiBob3cgdGhlIGZvbGRzIGFyZSBjcmVhdGVkOgoKYGBge3IsIGVjaG89RkFMU0UsIG91dC53aWR0aD0iODAwcHgifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWciLCAidmZvbGQucG5nIikpCmBgYAoKKipOb3RlKio6IFBlb3BsZSB0eXBpY2FsbHkgaWdub3JlIHNwYXRpYWwgZGVwZW5kZW5jZSB3aXRoIGNyb3NzIHZhbGlkYXRpb24gb2YgYWlyIHBvbGx1dGlvbiBtb25pdG9yaW5nIGRhdGEgaW4gdGhlIGFpciBwb2xsdXRpb24gZmllbGQsIHNvIHdlIHdpbGwgZG8gdGhlIHNhbWUuICBIb3dldmVyLCBpdCBtaWdodCBtYWtlIHNlbnNlIHRvIGxlYXZlIG91dCBibG9ja3Mgb2YgbW9uaXRvcnMgcmF0aGVyIHRoYW4gIHJhbmRvbSBpbmRpdmlkdWFsIG1vbml0b3JzIHRvIGhlbHAgYWNjb3VudCBmb3Igc29tZSBzcGF0aWFsIGRlcGVuZGVuY2UuCgojIyMgQ3JlYXRpbmcgdGhlICR2JC1mb2xkcyB1c2luZyBgcnNhbXBsZWAKClRoZSBbYHZmb2xkX2N2KClgXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3JzYW1wbGUvcmVmZXJlbmNlL3Zmb2xkX2N2Lmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gZnVuY3Rpb24gb2YgdGhlIGByc2FtcGxlYCBwYWNrYWdlIGNhbiBiZSB1c2VkIHRvIHBhcnNlIHRoZSB0cmFpbmluZyBkYXRhIGludG8gZm9sZHMgZm9yICR2JC1mb2xkIGNyb3NzIHZhbGlkYXRpb24uCgotIFRoZSBgdmAgYXJndW1lbnQgc3BlY2lmaWVzIHRoZSBudW1iZXIgb2YgZm9sZHMgdG8gY3JlYXRlLgotIFRoZSBgcmVwZWF0c2AgYXJndW1lbnQgc3BlY2lmaWVzIGlmIGFueSBzYW1wbGVzIHNob3VsZCBiZSByZXBlYXRlZCBhY3Jvc3MgZm9sZHMgLSBkZWZhdWx0IGlzIGBGQUxTRWAKLSBUaGUgYHN0cmF0YWAgYXJndW1lbnQgc3BlY2lmaWVzIGEgdmFyaWFibGUgdG8gc3RyYXRpZnkgc2FtcGxlcyBhY3Jvc3MgZm9sZHMgLSBqdXN0IGxpa2UgaW4gYGluaXRpYWxfc3BsaXQoKWAuCgpBZ2FpbiwgYmVjYXVzZSB0aGVzZSBhcmUgY3JlYXRlZCBhdCByYW5kb20sIHdlIG5lZWQgdG8gdXNlIHRoZSBiYXNlIGBzZXQuc2VlZCgpYCBmdW5jdGlvbiBpbiBvcmRlciB0byBvYnRhaW4gdGhlIHNhbWUgcmVzdWx0cyBlYWNoIHRpbWUgd2Uga25pdCB0aGlzIGRvY3VtZW50LiAKR2VuZXJhbGx5IHNwZWFraW5nIHVzaW5nIDEwIGZvbGRzIGlzIGdvb2QgcHJhY3RpY2UsIGJ1dCB0aGlzIGRlcGVuZHMgb24gdGhlIHZhcmlhYmlsaXR5IHdpdGhpbiB5b3VyIGRhdGEuIApXZSBhcmUgZ29pbmcgdG8gdXNlIDQgZm9yIHRoZSBzYWtlIG9mIGV4cGVkaWVuY3kuIAoKYGBge3J9CnNldC5zZWVkKDEyMzQpCnZmb2xkX3BtIDwtIHJzYW1wbGU6OnZmb2xkX2N2KGRhdGEgPSB0cmFpbl9wbSwgdiA9IDQpCnZmb2xkX3BtCnB1bGwodmZvbGRfcG0sIHNwbGl0cykKYGBgCgpOb3cgd2UgY2FuIHNlZSB0aGF0IHdlIGhhdmUgY3JlYXRlZCA0IGZvbGRzIG9mIHRoZSBkYXRhIGFuZCB3ZSBjYW4gc2VlIGhvdyBtYW55IHZhbHVlcyB3ZXJlIHNldCBhc2lkZSBmb3IgdGVzdGluZyAoY2FsbGVkIGFzc2Vzc2luZyBmb3IgY3Jvc3MgdmFsaWRhdGlvbiBzZXRzKSBhbmQgdHJhaW5pbmcgKGNhbGxlZCBhbmFseXNpcyBmb3IgY3Jvc3MgdmFsaWRhdGlvbiBzZXRzKSB3aXRoaW4gZWFjaCBmb2xkLgoKT25jZSB0aGUgZm9sZHMgYXJlIGNyZWF0ZWQgdGhleSBjYW4gYmUgdXNlZCB0byBldmFsdWF0ZSBwZXJmb3JtYW5jZSBieSBmaXR0aW5nIHRoZSBtb2RlbCB0byBlYWNoIG9mIHRoZSByZS1zYW1wbGVzIHRoYXQgd2UgY3JlYXRlZDoKCmBgYHtyLCBlY2hvPUZBTFNFLCBvdXQud2lkdGg9IjgwMHB4In0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwgImNyb3NzX3ZhbGlkYXRpb24ucG5nIikpCmBgYAoKIyMjIEFzc2Vzc2luZyBtb2RlbCBwZXJmb3JtYW5jZSBvbiAkdiQtZm9sZHMgdXNpbmcgYHR1bmVgCgpXZSBjYW4gZml0IHRoZSBtb2RlbCB0byBvdXIgY3Jvc3MgdmFsaWRhdGlvbiBmb2xkcyB1c2luZyB0aGUgYGZpdF9yZXNhbXBsZXMoKWAgZnVuY3Rpb24gb2YgdGhlIGB0dW5lYCBwYWNrYWdlLCBieSBzcGVjaWZ5aW5nIG91ciBgd29ya2Zsb3dgIG9iamVjdCBhbmQgdGhlIGNyb3NzIHZhbGlkYXRpb24gZm9sZCBvYmplY3Qgd2UganVzdCBjcmVhdGVkLiAKU2VlIFtoZXJlXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3R1bmUvcmVmZXJlbmNlL2ZpdF9yZXNhbXBsZXMuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSBmb3IgbW9yZSBpbmZvcm1hdGlvbi4KCgpgYGB7cn0KcmVzYW1wbGVfZml0IDwtIHR1bmU6OmZpdF9yZXNhbXBsZXMoUE1fd2Zsb3csIHZmb2xkX3BtKQpgYGAKCldlIGNhbiBub3cgdGFrZSBhIGxvb2sgYXQgdmFyaW91cyBwZXJmb3JtYW5jZSBtZXRyaWNzIGJhc2VkIG9uIHRoZSBmaXQgb2Ygb3VyIGNyb3NzIHZhbGlkYXRpb24gInJlc2FtcGxlcyIuIApUbyBkbyB0aGlzIHdlIHdpbGwgdXNlIHRoZSBgc2hvd19iZXN0KClgIGZ1bmN0aW9uIG9mIHRoZSBgdHVuZWAgcGFja2FnZS4KCmBgYHtyfQp0dW5lOjpzaG93X2Jlc3QocmVzYW1wbGVfZml0LCBtZXRyaWMgPSAicm1zZSIpCmBgYAoKSGVyZSB3ZSBjYW4gc2VlIHRoZSBtZWFuIGBSTVNFYCB2YWx1ZSBhY3Jvc3MgYWxsIGZvdXIgZm9sZHMuIFRoZSBmdW5jdGlvbiBpcyBjYWxsZWQgYHNob3dfYmVzdCgpYCBiZWNhdXNlIGl0IGlzIGFsc28gdXNlZCBmb3IgbW9kZWwgdHVuaW5nIGFuZCBpdCB3aWxsIHNob3cgdGhlIHBhcmFtZXRlciBjb21iaW5hdGlvbiB3aXRoIHRoZSBiZXN0IHBlcmZvcm1hbmNlLCB3ZSB3aWxsIGRpc2N1c3MgdGhpcyBtb3JlIGxhdGVyIGluIHRoZSBjYXNlIHN0dWR5LgoKCiMgKipEYXRhIEFuYWx5c2lzKioKKioqCgpJbiB0aGUgcHJldmlvdXMgc2VjdGlvbiwgd2UgZGVtb25zdHJhdGVkIGhvdyB0byBidWlsZCBhIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWwgKHNwZWNpZmljYWxseSBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsKSB0byBwcmVkaWN0IGFpciBwb2xsdXRpb24gd2l0aCB0aGUgYHRpZHltb2RlbHNgIGZyYW1ld29yay4gCgpJbiB0aGUgbmV4dCBmZXcgc2VjdGlvbiwgd2Ugd2lsbCBkZW1vbnN0cmF0ZSBhbm90aGVyIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWwuIAoKCiMjIFJhbmRvbSBGb3Jlc3QKCk5vdywgd2UgYXJlIGdvaW5nIHRvIHByZWRpY3Qgb3VyIG91dGNvbWUgdmFyaWFibGUgKGFpciBwb2xsdXRpb24pIHVzaW5nIGEgZGVjaXNpb24gdHJlZSBtZXRob2QgY2FsbGVkIFtyYW5kb20gZm9yZXN0XShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9SYW5kb21fZm9yZXN0KXt0YXJnZXQ9Il9ibGFuayJ9LgoKQSBbZGVjaXNpb24gdHJlZV0oaHR0cHM6Ly9tZWRpdW0uY29tL2dyZXlhdG9tL2RlY2lzaW9uLXRyZWVzLWEtc2ltcGxlLXdheS10by12aXN1YWxpemUtYS1kZWNpc2lvbi1kYzUwNmE0MDNhZWIpe3RhcmdldD0iX2JsYW5rIn0gaXMgYSB0b29sIHRvIHBhcnRpdGlvbiBkYXRhIG9yIGFueXRoaW5nIHJlYWxseSwgYmFzZWQgb24gYSBzZXJpZXMgb2Ygc2VxdWVudGlhbCAob2Z0ZW4gYmluYXJ5KSBkZWNpc2lvbnMsIHdoZXJlIHRoZSBkZWNpc2lvbnMgYXJlIGNob3NlbiBiYXNlZCBvbiB0aGVpciBhYmlsaXR5IHRvIG9wdGltYWxseSBzcGxpdCB0aGUgZGF0YS4KCkhlcmUgeW91IGNhbiBzZWUgYSBzaW1wbGUgZXhhbXBsZToKCmBgYHtyLCBlY2hvID0gRkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJodHRwczovL21pcm8ubWVkaXVtLmNvbS9tYXgvMTAwMC8xKkxNb0ptWENzUWxjaUdURXlvU04zOWcuanBlZyIpCmBgYAoKIyMjIyMgW1tzb3VyY2VdXShodHRwczovL3Rvd2FyZHNkYXRhc2NpZW5jZS5jb20vdW5kZXJzdGFuZGluZy1yYW5kb20tZm9yZXN0LTU4MzgxZTA2MDJkMil7dGFyZ2V0PSJfYmxhbmsifQoKSW4gdGhlIGNhc2Ugb2YgW3JhbmRvbSBmb3Jlc3RdKGh0dHBzOi8vdG93YXJkc2RhdGFzY2llbmNlLmNvbS9kZWNpc2lvbi10cmVlLWVuc2VtYmxlcy1iYWdnaW5nLWFuZC1ib29zdGluZy0yNjZhOGJhNjBmZDkpe3RhcmdldD0iX2JsYW5rIn0sIG11bHRpcGxlIGRlY2lzaW9uIHRyZWVzIGFyZSBjcmVhdGVkIC0gaGVuY2UgdGhlIG5hbWUgZm9yZXN0LCBhbmQgZWFjaCB0cmVlIGlzIGJ1aWx0IHVzaW5nIGEgcmFuZG9tIHN1YnNldCBvZiB0aGUgdHJhaW5pbmcgZGF0YSAod2l0aCByZXBsYWNlbWVudCkgLSBoZW5jZSB0aGUgZnVsbCBuYW1lIHJhbmRvbSBmb3Jlc3QuIFRoaXMgcmFuZG9tIGFzcGVjdCBoZWxwcyB0byBrZWVwIHRoZSBhbGdvcml0aG0gZnJvbSBvdmVyZml0dGluZyB0aGUgZGF0YS4KClRoZSBtZWFuIG9mIHRoZSBwcmVkaWN0aW9ucyBmcm9tIGVhY2ggb2YgdGhlIHRyZWVzIGlzIHVzZWQgaW4gdGhlIGZpbmFsIG91dHB1dC4KCmBgYHtyLCBlY2hvID0gRkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJodHRwczovL21pcm8ubWVkaXVtLmNvbS9tYXgvMTQwMC8wKmZfcVFQRnBkb2ZXR0xRcWMucG5nIikKYGBgCgoKSW4gb3VyIGNhc2UsIHdlIGFyZSBnb2luZyB0byB1c2UgdGhlIHJhbmRvbSBmb3Jlc3QgbWV0aG9kIG9mIHRoZSB0aGUgYHJhbmRvbUZvcmVzdGAgcGFja2FnZS4gCgpUaGlzIHBhY2thZ2UgaXMgY3VycmVudGx5IG5vdCBjb21wYXRpYmxlIHdpdGggY2F0ZWdvcmljYWwgdmFyaWFibGVzIHRoYXQgaGF2ZSBtb3JlIHRoYW4gNTMgbGV2ZWxzLiBTZWUgW2hlcmVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9yYW5kb21Gb3Jlc3QvTkVXUykgZm9yIHRoZSBkb2N1bWVudGF0aW9uIGFib3V0IHdoZW4gdGhpcyB3YXMgdXBkYXRlZCBmcm9tIDI1IGxldmVscy4gVGh1cyB3ZSB3aWxsIHRvIHJlbW92ZSB0aGUgYHpjdGFgICBhbmQgYGNvdW50eWAgdmFyaWFibGVzLgoKTm90ZSB0aGF0IHRoZSBgc3RlcF9ub3ZlbCgpYCBmdW5jdGlvbiBpcyBuZWNlc3NhcnkgaGVyZSBmb3IgYHN0YXRlYCB0byBnZXQgYWxsIGNyb3NzIHZhbGlkYXRpb24gZm9sZHMgdG8gd29yaywgYmVjYXVzZSB0aGVyZSB3aWxsIGJlIGRpZmZlcmVudCBsZXZlbHMgaW5jbHVkZWQgaW4gZWFjaCBmb2xkIHRlc3QgYW5kIHRyYWluaW5nIHNldHMsIHRodXMgdGhlcmUgYXJlIG5ldyBsZXZlbHMgZm9yIHNvbWUgb2YgdGhlIHRlc3Qgc2V0cyB3aGljaCB3aWxsIHJlc3VsdCBpbiBhbiBlcnJvci4gIAoKQWNjb3JkaW5nIHRvIHRoZSBbZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3JlY2lwZXMvdmVyc2lvbnMvMC4xLjEzL3RvcGljcy9zdGVwX25vdmVsKSBmb3IgdGhlIGByZWNpcGVzYCBwYWNrYWdlOgoKPiBzdGVwX25vdmVsIGNyZWF0ZXMgYSBzcGVjaWZpY2F0aW9uIG9mIGEgcmVjaXBlIHN0ZXAgdGhhdCB3aWxsIGFzc2lnbiBhIHByZXZpb3VzbHkgdW5zZWVuIGZhY3RvciBsZXZlbCB0byBhIG5ldyB2YWx1ZS4KCmBgYHtyfQpSRl9yZWMgPC0gcmVjaXBlKHRyYWluX3BtKSAlPiUKICAgIHVwZGF0ZV9yb2xlKGV2ZXJ5dGhpbmcoKSwgbmV3X3JvbGUgPSAicHJlZGljdG9yIiklPiUKICAgIHVwZGF0ZV9yb2xlKHZhbHVlLCBuZXdfcm9sZSA9ICJvdXRjb21lIiklPiUKICAgIHVwZGF0ZV9yb2xlKGlkLCBuZXdfcm9sZSA9ICJpZCB2YXJpYWJsZSIpICU+JQogICAgdXBkYXRlX3JvbGUoImZpcHMiLCBuZXdfcm9sZSA9ICJjb3VudHkgaWQiKSAlPiUKICAgIHN0ZXBfbm92ZWwoInN0YXRlIikgJT4lCiAgICBzdGVwX3N0cmluZzJmYWN0b3IoInN0YXRlIiwgImNvdW50eSIsICJjaXR5IikgJT4lCiAgICBzdGVwX3JtKCJjb3VudHkiKSAlPiUKICAgIHN0ZXBfcm0oInpjdGEiKSAlPiUKICAgIHN0ZXBfY29ycihhbGxfbnVtZXJpYygpKSU+JQogICAgc3RlcF9uenYoYWxsX251bWVyaWMoKSkKYGBgCgpUaGUgYHJhbmRfZm9yZXN0KClgIGZ1bmN0aW9uIG9mIHRoZSBgcGFyc25pcGAgcGFja2FnZSBoYXMgdGhyZWUgaW1wb3J0YW50IGFyZ3VtZW50cyB0aGF0IGFjdCBhcyBhbiBpbnRlcmZhY2UgZm9yIHRoZSBkaWZmZXJlbnQgcG9zc2libGUgZW5naW5lcyB0byBwZXJmb3JtIGEgcmFuZG9tIGZvcmVzdCBhbmFseXNpczoKCjEuIGBtdHJ5YCAtIFRoZSBudW1iZXIgb2YgcHJlZGljdG9yIHZhcmlhYmxlcyAob3IgZmVhdHVyZXMpIHRoYXQgd2lsbCBiZSByYW5kb21seSBzYW1wbGVkIGF0IGVhY2ggc3BsaXQgd2hlbiBjcmVhdGluZyB0aGUgdHJlZSBtb2RlbHMuIFRoZSBkZWZhdWx0IG51bWJlciBmb3IgcmVncmVzc2lvbiBhbmFseXNlcyBpcyB0aGUgbnVtYmVyIG9mIHByZWRpY3RvcnMgZGl2aWRlZCBieSAzLiAKMi4gYG1pbl9uYCAtIFRoZSBtaW5pbXVtIG51bWJlciBvZiBkYXRhIHBvaW50cyBpbiBhIG5vZGUgdGhhdCBhcmUgcmVxdWlyZWQgZm9yIHRoZSBub2RlIHRvIGJlIHNwbGl0IGZ1cnRoZXIuCjMuIGB0cmVlc2AgLSB0aGUgbnVtYmVyIG9mIHRyZWVzIGluIHRoZSBlbnNlbWJsZQoKV2Ugd2lsbCBzdGFydCBieSB0cnlpbmcgYW4gYG10cnlgIHZhbHVlIG9mIDEwIGFuZCBhIGBtaW5fbmAgdmFsdWUgb2YgMy4KCk5vdyB0aGF0IHdlIGhhdmUgb3VyIHJlY2lwZSAoYFJGX3JlY2ApLCBsZXQncyBzcGVjaWZ5IHRoZSBtb2RlbCB3aXRoIGByYW5kX2ZvcmVzdCgpYCBmcm9tIGBwYXJzbmlwYCB3aXRoIHRoZSBgbW9kZSA9ICJyZWdyZXNzaW9uImAgYXJndW1lbnQgdG8gc3BlY2lmeSBvdXIgb3V0Y29tZSB2YXJpYWJsZSAoYWlyIHBvbGx1dGlvbikgaXMgY29udGludW91cy4gCgpgYGB7cn0KUE10cmVlX21vZGVsIDwtIAogIHBhcnNuaXA6OnJhbmRfZm9yZXN0KG10cnkgPSAxMCwgbWluX24gPSAzLCAKICAgICAgICAgICAgICAgICAgICAgICBtb2RlID0gInJlZ3Jlc3Npb24iKQpQTXRyZWVfbW9kZWwKYGBgCgpOZXh0LCB3ZSBzZXQgdGhlIGVuZ2luZSBhbmQgbW9kZToKCk5vdGUgdGhhdCB5b3UgY291bGQgYWxzbyB1c2UgdGhlIGByYW5nZXJgIG9yIGBzcGFya2AgcGFja2FnZXMgaW5zdGVhZCBvZiBgcmFuZG9tRm9yZXN0YC4KSWYgeW91IHdlcmUgdG8gdXNlIHRoZSBgcmFuZ2VyYCBwYWNrYWdlIHRvIGltcGxlbWVudCB0aGUgcmFuZG9tIGZvcmVzdCBhbmFseXNpcyB5b3Ugd291bGQgbmVlZCB0byBzcGVjaWZ5IGFuIGBpbXBvcnRhbmNlYCBhcmd1bWVudCB0byBiZSBhYmxlIHRvIGV2YWx1YXRlIHByZWRpY3RvciBpbXBvcnRhbmNlLiAgVGhlIG9wdGlvbnMgYXJlIGBpbXB1cml0eWAgb3IgYHBlcm11dGF0aW9uYC4KClRoZXNlIG90aGVyIHBhY2thZ2VzIGhhdmUgZGlmZmVyZW50IGFkdmFudGFnZXMgYW5kIGRpc2FkdmFudGFnZXMtIGZvciBleGFtcGxlIGByYW5nZXJgIGFuZCBgc3BhcmtgIGFyZSBub3QgYXMgbGltaXRpbmcgZm9yIHRoZSBudW1iZXIgb2YgY2F0ZWdvcmllcyBmb3IgY2F0ZWdvcmljYWwgdmFyaWFibGVzLiBGb3IgbW9yZSBpbmZvcm1hdGlvbiBzZWUgdGhlaXIgZG9jdW1lbnRhdGlvbjogW2hlcmVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9yYW5nZXIvcmFuZ2VyLnBkZikgZm9yIHJhbmdlciwgW2hlcmVdKGh0dHA6Ly9zcGFyay5hcGFjaGUub3JnL2RvY3MvbGF0ZXN0L21sbGliLWVuc2VtYmxlcy5odG1sI3JhbmRvbS1mb3Jlc3RzKSBmb3IgYHNwYXJrYCwgYW5kIFtoZXJlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcmFuZG9tRm9yZXN0L3JhbmRvbUZvcmVzdC5wZGYpIGZvciBgcmFuZG9tRm9yZXN0YC4KClNlZSBbaGVyZV0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yYW5kX2ZvcmVzdC5odG1sKSBmb3IgbW9yZSBkb2N1bWVudGF0aW9uIGFib3V0IGltcGxlbWVudGluZyB0aGVzZSBlbmdpbmUgb3B0aW9ucyB3aXRoIHRpZHltb2RlbHMuIE5vdGUgdGhhdCB0aGVyZSBhcmUgYWxzbyBbb3RoZXJdKGh0dHBzOi8vd3d3LmxpbmtlZGluLmNvbS9wdWxzZS9kaWZmZXJlbnQtcmFuZG9tLWZvcmVzdC1wYWNrYWdlcy1yLW1hZGh1ci1tb2RpLykgUiBwYWNrYWdlcyBmb3IgaW1wbGVtZW50aW5nIHJhbmRvbSBmb3Jlc3QgYWxnb3JpdGhtcywgYnV0IHRoZXNlIHRocmVlIHBhY2thZ2VzIChgcmFuZ2VyYCwgYHNwYXJrYCwgYW5kIGByYW5kb21Gb3Jlc3RgKSBhcmUgY3VycmVudGx5IGNvbXBhdGlibGUgd2l0aCBgdGlkeW1vZGVsc2AuCgpgYGB7cn0KClJGX1BNX21vZGVsIDwtIAogIFBNdHJlZV9tb2RlbCAlPiUKICBzZXRfZW5naW5lKCJyYW5kb21Gb3Jlc3QiKSAlPiUKICBzZXRfbW9kZSgicmVncmVzc2lvbiIpCgpSRl9QTV9tb2RlbApgYGAKClRoZW4sIHdlIHB1dCB0aGlzIGFsbCB0b2dldGhlciBpbnRvIGEgYHdvcmtmbG93YDogCgojIyMjIHsucmVjYWxsX2NvZGVfcXVlc3Rpb25fYmxvY2t9CjxiPjx1PiBRdWVzdGlvbiBPcHBvcnR1bml0eSA8L3U+PC9iPgoKU2VlIGlmIHlvdSBjYW4gY29tZSB1cCB3aXRoIHRoZSBjb2RlIHRvIGRvIHRoaXMuCgojIyMjCgoqKioKPGRldGFpbHM+IDxzdW1tYXJ5PiBDbGljayBoZXJlIHRvIHJldmVhbCB0aGUgYW5zd2VyLiA8L3N1bW1hcnk+CgpgYGB7cn0KUkZfd2Zsb3cgPC0gd29ya2Zsb3dzOjp3b3JrZmxvdygpICU+JQogICAgICAgICAgICB3b3JrZmxvd3M6OmFkZF9yZWNpcGUoUkZfcmVjKSAlPiUKICAgICAgICAgICAgd29ya2Zsb3dzOjphZGRfbW9kZWwoUkZfUE1fbW9kZWwpCgpgYGAKPC9kZXRhaWxzPiAKKioqCgpgYGB7cn0KUkZfd2Zsb3cKYGBgCgoKRmluYWxseSwgd2UgZml0IHRoZSBkYXRhIHRvIHRoZSBtb2RlbDoKIyMjIyB7LnJlY2FsbF9jb2RlX3F1ZXN0aW9uX2Jsb2NrfQo8Yj48dT4gUXVlc3Rpb24gT3Bwb3J0dW5pdHkgPC91PjwvYj4KCkRvIHlvdSByZWNhbGwgaG93IHRvIGRvIHRoaXM/CgojIyMjCgoqKioKPGRldGFpbHM+IDxzdW1tYXJ5PiBDbGljayBoZXJlIHRvIHJldmVhbCB0aGUgYW5zd2VyLiA8L3N1bW1hcnk+CgpgYGB7cn0KUkZfd2Zsb3dfZml0IDwtIHBhcnNuaXA6OmZpdChSRl93ZmxvdywgZGF0YSA9IHRyYWluX3BtKQpgYGAKPC9kZXRhaWxzPiAKKioqCgpgYGB7cn0KUkZfd2Zsb3dfZml0CmBgYAoKCkxldCdzIHRha2UgYSBsb29rIGF0IHRoZSB0b3AgMTAgY29udHJpYnV0aW5nIHZhcmlhYmxlczoKCgojIyMjIHsucmVjYWxsX2NvZGVfcXVlc3Rpb25fYmxvY2t9CjxiPjx1PiBRdWVzdGlvbiBPcHBvcnR1bml0eSA8L3U+PC9iPgoKU2VlIGlmIHlvdSBjYW4gcmVjYWxsIGhvdyB0byBkbyB0aGlzLgoKIyMjIwoKYGBge3IsIGVjaG8gPSBGQUxTRX0KUkZfd2Zsb3dfZml0ICU+JSAKICBwdWxsX3dvcmtmbG93X2ZpdCgpICU+JSAKICB2aXAobnVtX2ZlYXR1cmVzID0gMTApCgpgYGAKCgoqKioKPGRldGFpbHM+IDxzdW1tYXJ5PiBDbGljayBoZXJlIHRvIHJldmVhbCB0aGUgYW5zd2VyLiA8L3N1bW1hcnk+CgpgYGB7cn0KUkZfd2Zsb3dfZml0ICU+JSAKICBwdWxsX3dvcmtmbG93X2ZpdCgpICU+JSAKICB2aXAobnVtX2ZlYXR1cmVzID0gMTApCmBgYAo8L2RldGFpbHM+CioqKgoKCkludGVyZXN0aW5nISBJbiB0aGUgcHJldmlvdXMgbW9kZWwgdGhlIENNQVEgdmFsdWVzIGFuZCB0aGUgc3RhdGUgd2hlcmUgdGhlIG1vbml0b3Igd2FzIGxvY2F0ZWQgd2VyZSBhbHNvIHRoZSB0b3AgdHdvIG1vc3QgaW1wb3J0YW50LCBob3dldmVyIHByZWRpY3RvcnMgYWJvdXQgZWR1Y2F0aW9uIGxldmVscyBvZiB0aGUgY29tbXVuaXRpZXMgd2hlcmUgdGhlIG1vbml0b3Igd2FzIGxvY2F0ZWQgd2FzIGFtb25nIHRoZSB0b3AgbW9zdCBpbXBvcnRhbnQuIE5vdyB3ZSBzZWUgdGhhdCBwb3B1bGF0aW9uIGRlbnNpdHkgYW5kIHByb3hpbWl0eSB0byBzb3VyY2VzIG9mIGVtaXNzaW9ucyBhbmQgcm9hZHMgYXJlIGFtb25nIHRoZSB0b3AgdGVuLgoKCk5vdyBsZXQncyB0YWtlIGEgbG9vayBhdCBtb2RlbCBwZXJmb3JtYW5jZSBieSBmaXR0aW5nIHRoZSBkYXRhIHVzaW5nIGNyb3NzIHZhbGlkYXRpb246CgojIyMjIHsucmVjYWxsX2NvZGVfcXVlc3Rpb25fYmxvY2t9CjxiPjx1PiBRdWVzdGlvbiBPcHBvcnR1bml0eSA8L3U+PC9iPgoKU2VlIGlmIHlvdSBjYW4gcmVjYWxsIGhvdyB0byBkbyB0aGlzLgoKIyMjIwoKKioqCjxkZXRhaWxzPiA8c3VtbWFyeT4gQ2xpY2sgaGVyZSB0byByZXZlYWwgdGhlIGFuc3dlci4gPC9zdW1tYXJ5PgoKYGBge3IsIGV2YWwgPSBGQUxTRX0Kc2V0LnNlZWQoNDU2KQpyZXNhbXBsZV9SRl9maXQgPC0gdHVuZTo6Zml0X3Jlc2FtcGxlcyhSRl93ZmxvdywgdmZvbGRfcG0pCmNvbGxlY3RfbWV0cmljcyhyZXNhbXBsZV9SRl9maXQpCmBgYAoKPC9kZXRhaWxzPgoqKioKCmBgYHtyLCBlY2hvID0gRkFMU0V9CnNldC5zZWVkKDQ1NikKcmVzYW1wbGVfUkZfZml0IDwtIHR1bmU6OmZpdF9yZXNhbXBsZXMoUkZfd2Zsb3csIHZmb2xkX3BtKQpjb2xsZWN0X21ldHJpY3MocmVzYW1wbGVfUkZfZml0KQpgYGAKCk5vdyBsZXQncyBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGlzIG1vZGVsIHdpdGggb3VyIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsOgoKYGBge3J9CiMgb3VyIGluaXRpYWwgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWw6CmNvbGxlY3RfbWV0cmljcyhyZXNhbXBsZV9maXQpCmBgYAoKT0ssIHNvIG91ciBmaXJzdCBtb2RlbCBoYWQgYSBtZWFuIGBybXNlYCB2YWx1ZSBvZiAyLjE3LgpJdCBsb29rcyBsaWtlIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsIGhhZCAgYSBtdWNoIGxvd2VyIGBybXNlYCB2YWx1ZSBvZiAxLjcyLgoKCiMjIyMgey50aGlua19xdWVzdGlvbl9ibG9ja30KPGI+PHU+IFF1ZXN0aW9uIE9wcG9ydHVuaXR5IDwvdT48L2I+CgpEbyB5b3UgcmVjYWxsIGhvdyB0aGUgUk1TRSBpcyBjYWxjdWxhdGVkPwojIyMjCgoqKioKCjxkZXRhaWxzPiA8c3VtbWFyeT4gQ2xpY2sgaGVyZSB0byByZXZlYWwgdGhlIGFuc3dlci4gPC9zdW1tYXJ5PgokJFJNU0UgPSBcc3FydHtcZnJhY3tcc3VtX3tpPTF9XntufXsoXGhhdHt5X3R9LSB5X3QpfV4yfXtufX0kJAo8L2RldGFpbHM+IAoKSWYgd2UgdHVuZWQgb3VyIHJhbmRvbSBmb3Jlc3QgbW9kZWwgYmFzZWQgb24gdGhlIG51bWJlciBvZiB0cmVlcyBvciB0aGUgdmFsdWUgZm9yIGBtdHJ5YCAod2hpY2ggaXMgIlRoZSBudW1iZXIgb2YgcHJlZGljdG9ycyB0aGF0IHdpbGwgYmUgcmFuZG9tbHkgc2FtcGxlZCBhdCBlYWNoIHNwbGl0IHdoZW4gY3JlYXRpbmcgdGhlIHRyZWUgbW9kZWxzIiksIHdlIG1pZ2h0IGdldCBhIG1vZGVsIHdpdGggZXZlbiBiZXR0ZXIgcGVyZm9ybWFuY2UuCgpIb3dldmVyLCBvdXIgY3Jvc3MgdmFsaWRhdGVkIG1lYW4gcm1zZSB2YWx1ZSBvZiAxLjcyIGlzIHF1aXRlIGdvb2QgYmVjYXVzZSBvdXIgcmFuZ2Ugb2YgdHJ1ZSBvdXRjb21lIHZhbHVlcyBpcyBtdWNoIGxhcmdlcjogKGByIHJvdW5kKHJhbmdlKHRlc3RfcG0kdmFsdWUpLDMpYCkuCgoKIyMgTW9kZWwgdHVuaW5nCgpbSHlwZXJwYXJhbWV0ZXJzXShodHRwczovL21hY2hpbmVsZWFybmluZ21hc3RlcnkuY29tL2RpZmZlcmVuY2UtYmV0d2Vlbi1hLXBhcmFtZXRlci1hbmQtYS1oeXBlcnBhcmFtZXRlci8pIGFyZSBvZnRlbiB0aGluZ3MgdGhhdCB3ZSBuZWVkIHRvIHNwZWNpZnkgYWJvdXQgYSBtb2RlbC4gRm9yIGV4YW1wbGUsIHRoZSBudW1iZXIgb2YgcHJlZGljdG9yIHZhcmlhYmxlcyAob3IgZmVhdHVyZXMpIHRoYXQgd2lsbCBiZSByYW5kb21seSBzYW1wbGVkIGF0IGVhY2ggc3BsaXQgd2hlbiBjcmVhdGluZyB0aGUgdHJlZSBtb2RlbHMgY2FsbGVkIGBtdHJ5YCBpcyBhIGh5cGVycGFyYW1ldGVyLiBUaGUgZGVmYXVsdCBudW1iZXIgZm9yIHJlZ3Jlc3Npb24gYW5hbHlzZXMgaXMgdGhlIG51bWJlciBvZiBwcmVkaWN0b3JzIGRpdmlkZWQgYnkgMy4gSW5zdGVhZCBvZiBhcmJpdHJhcmlseSBzcGVjaWZ5aW5nIHRoaXMsIHdlIGNhbiB0cnkgdG8gZGV0ZXJtaW5lIHRoZSBiZXN0IG9wdGlvbiBmb3IgbW9kZWwgcGVyZm9ybWFuY2UgYnkgYSBwcm9jZXNzIGNhbGxlZCB0dW5pbmcuIAoKCk5vdyBsZXQncyB0cnkgc29tZSB0dW5pbmcuCgpMZXQncyB0YWtlIGEgY2xvc2VyIGxvb2sgYXQgdGhlIGBtdHJ5YCBhbmQgYG1pbl9uYCBoeXBlcnBhcmFtZXRycyBpbiBvdXIgUmFuZG9tIEZvcmVzdCBtb2RlbC4KCldlIGFyZW4ndCBleGFjdGx5IHN1cmUgd2hhdCB2YWx1ZXMgb2YgYG10cnlgIGFuZCBgbWluX25gIGFjaGlldmUgZ29vZCBhY2N1cmFjeSB5ZXQga2VlcCBvdXIgbW9kZWwgZ2VuZXJhbGl6YWJsZSBmb3Igb3RoZXIgZGF0YS4KClRoaXMgaXMgd2hlbiBvdXIgY3Jvc3MgdmFsaWRhdGlvbiBtZXRob2RzIGJlY29tZSByZWFsbHkgaGFuZHkgYmVjYXVzZSBub3cgd2UgY2FuIHRlc3Qgb3V0IGRpZmZlcmVudCB2YWx1ZXMgZm9yIGVhY2ggb2YgdGhlc2UgaHlwZXJwYXJhbWV0ZXJzIHRvIGFzc2VzcyB3aGF0IHZhbHVlcyBzZWVtIHRvIHdvcmsgYmVzdCBmb3IgbW9kZWwgcGVyZm9ybWFuY2Ugb24gdGhlc2UgcmVzYW1wbGVzIG9mIG91ciB0cmFpbmluZyBzZXQgZGF0YS4KClByZXZpb3VzbHkgd2Ugc3BlY2lmaWVkIG91ciBtb2RlbCBsaWtlIHNvOgpgYGB7cn0KUkZfUE1fbW9kZWwgPC0gCiAgcGFyc25pcDo6cmFuZF9mb3Jlc3QobXRyeSA9IDEwLCBtaW5fbiA9IDMsIAogICAgICAgICAgICAgICAgICAgICAgIG1vZGUgPSAicmVncmVzc2lvbiIpICU+JQogIHNldF9lbmdpbmUoInJhbmRvbUZvcmVzdCIpICU+JQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikKClJGX1BNX21vZGVsCmBgYApOb3cgaW5zdGVhZCBvZiBzcGVjaWZ5aW5nIGEgdmFsdWUgZm9yIHRoZSBgbXRyeWAgYW5kIGBtaW5fbmAgYXJndW1lbnRzLCB3ZSBjYW4gdXNlIHRoZSBgdHVuZSgpYCBmdW5jdGlvbiBvZiB0aGUgYHR1bmVgIHBhY2thZ2UgbGlrZSBzbzogYG10cnkgPSB0dW5lKClgLiBUaGlzIGluZGljYXRlcyB0aGF0IHRoZXNlIGh5cGVycGFyYW1ldGVycyBhcmUgdG8gYmUgdHVuZWQuIAoKYGBge3J9Cgp0dW5lX1JGX21vZGVsIDwtIHJhbmRfZm9yZXN0KG10cnkgPSB0dW5lKCksIG1pbl9uID0gdHVuZSgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGUgPSAicmVncmVzc2lvbiIpICU+JQogIHNldF9lbmdpbmUoInJhbmRvbUZvcmVzdCIpICU+JQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikKICAgIAoKdHVuZV9SRl9tb2RlbAoKYGBgCgoKQWdhaW4gd2Ugd2lsbCBhZGQgdGhpcyB0byBhIHdvcmtmbG93LCB0aGUgb25seSBkaWZmZXJlbmNlIGhlcmUgaXMgdGhhdCB3ZSBhcmUgdXNpbmcgYSBkaWZmZXJlbnQgbW9kZWwgc3BlY2lmaWNhdGlvbiB3aXRoIGB0dW5lX1JGX21vZGVsYCBpbnN0ZWFkIG9mIGBSRl9tb2RlbGA6CgpgYGB7cn0KClJGX3R1bmVfd2Zsb3cgPC0gd29ya2Zsb3dzOjp3b3JrZmxvdygpICU+JQogICAgICAgICAgICB3b3JrZmxvd3M6OmFkZF9yZWNpcGUoUkZfcmVjKSAlPiUKICAgICAgICAgICAgd29ya2Zsb3dzOjphZGRfbW9kZWwodHVuZV9SRl9tb2RlbCkKUkZfdHVuZV93ZmxvdwoKYGBgCgoKTm93IHdlIGNhbiB1c2UgdGhlIGB0dW5lX2dyaWQoKWAgZnVuY3Rpb24gb2YgdGhlIGB0dW5lYCBwYWNrYWdlIHRvIGV2YWx1YXRlIGRpZmZlcmVudCBjb21iaW5hdGlvbnMgb2YgdmFsdWVzIGZvciBgbXRyeWAgYW5kIGBtaW5fbmAgdXNpbmcgb3VyIGNyb3NzIHZhbGlkYXRpb24gc2FtcGxlcyBvZiBvdXIgdHJhaW5pbmcgc2V0IChgdmZvbGRfcG1gKSB0byBzZWUgd2hhdCBjb21iaW5hdGlvbiBvZiB2YWx1ZXMgcGVyZm9ybXMgYmVzdC4KClRvIHVzZSB0aGlzIGZ1bmN0aW9uIHdlIHdpbGwgc3BlY2lmeSB0aGUgd29ya2Zsb3cgdXNpbmcgdGhlIGBvYmplY3RgIGFyZ3VtZW50ICBhbmQgdGhlIHNhbXBsZXMgdG8gdXNlIHVzaW5nIHRoZSBgcmVzYW1wbGVzYCBhcmd1bWVudC4gVGhlIGBncmlkYCBhcmd1bWVudCBzcGVjaWZpZXMgaG93IG1hbnkgcG9zc2libGUgb3B0aW9ucyBmb3IgZWFjaCBhcmd1bWVudCBzaG91bGQgYmUgYXR0ZW1wdGVkLgoKQnkgZGVmYXVsdCAxMCBkaWZmZXJlbnQgdmFsdWVzIHdpbGwgYmUgYXR0ZW1wdGVkIGZvciBlYWNoIGh5cGVycGFyYW1ldGVyIHRoYXQgaXMgYmVpbmcgdHVuZWQuCgpXZSBjYW4gdXNlIHRoZSBgZG9QYXJhbGxlbGAgcGFja2FnZSB0byBhbGxvdyB1cyB0byBmaXQgYWxsIHRoZXNlIG1vZGVscyB0byBvdXIgY3Jvc3MgdmFsaWRhdGlvbiBzYW1wbGVzIGZhc3Rlci4gU28gaWYgeW91IHdlcmUgcGVyZm9ybWluZyB0aGlzIG9uIGEgY29tcHV0ZXIgd2l0aCBtdWx0aXBsZSBjb3JlcyBvciBwcm9jZXNzb3JzLCB0aGVuIGRpZmZlcmVudCBtb2RlbHMgd2l0aCBkaWZmZXJlbnQgaHlwZXJwYXJhbWV0ZXIgdmFsdWVzIGNhbiBiZSBmaXQgdG8gdGhlIGNyb3NzIHZhbGlkYXRpb24gc2FtcGxlcyBzaW11bHRhbmVvdXNseSBhY3Jvc3MgZGlmZmVyZW50IGNvcmVzIG9yIHByb2Nlc3NvcnMuIAoKV2UgbmVlZCB0byB1c2UgYHNldC5zZWVkKClgIGhlcmUgYmVjYXVzZSB0aGUgdmFsdWVzIGNob3NlbiBmb3IgYG10cnlgIGFuZCBgbWluX25gIG1heSB2YXJ5IGlmIHdlIHByZWZvcm0gdGhpcyBldmFsdWF0aW9uIGFnYWluIGJlY2F1c2UgdGhleSBhcmUgY2hvc2VuIHNlbWktcmFuZG9tbHkgKG1lYW5pbmcgdGhhdCB0aGV5IGFyZSB3aXRoaW4gYSByYW5nZSBvZiByZWFzb25hYmxlIHZhbHVlcyBidXQgc3RpbGwgcmFuZG9tKS4KCk5vdGU6IHRoaXMgc3RlcCB3aWxsIHRha2Ugc29tZSB0aW1lLgoKYGBge3J9CmRvUGFyYWxsZWw6OnJlZ2lzdGVyRG9QYXJhbGxlbCgpCnNldC5zZWVkKDEyMykKdHVuZV9SRl9yZXN1bHRzIDwtIHR1bmVfZ3JpZChvYmplY3QgPSBSRl90dW5lX3dmbG93LCByZXNhbXBsZXMgPSB2Zm9sZF9wbSwgZ3JpZCA9IDIwKQp0dW5lX1JGX3Jlc3VsdHMKYGBgCgoKU2VlIFt0aGUgdHVuZSBnZXR0aW5nIHN0YXJ0ZWQgZ3VpZGUgXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3R1bmUvYXJ0aWNsZXMvZ2V0dGluZ19zdGFydGVkLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gZm9yIG1vcmUgaW5mb3JtYXRpb24gYWJvdXQgaW1wbGVtZW50aW5nIHRoaXMgaW4gYHRpZHltb2RlbHNgLgoKSWYgeW91IHdhbnRlZCBtb3JlIGNvbnRyb2wgb3ZlciB0aGlzIHByb2Nlc3MgeW91IGNvdWxkIHNwZWNpZnkgaG93IHRoZSBkaWZmZXJlbnQgcG9zc2libGUgb3B0aW9ucyBmb3IgYG10cnlgIGFuZCBgbWluX25gIGluIHRoZSBgdHVuZV9ncmlkKClgIGZ1bmN0aW9uIHVzaW5nIHRoZSBncmlkXyooKWAgZnVuY3Rpb25zIG9mIHRoZSBgZGlhbHNgIHBhY2thZ2UgdG8gY3JlYXRlIGEgbW9yZSBzcGVjaWZpYyBncmlkLgoKQmUgZGVmYXVsdCB0aGUgdmFsdWVzIGZvciB0aGUgaHlwZXJwYXJhbWV0ZXJzIGJlaW5nIHR1bmVkIGFyZSBjaG9zZW4gc2VtaS1yYW5kb21seSAobWVhbmluZyB0aGF0IHRoZXkgYXJlIHdpdGhpbiBhIHJhbmdlIG9mIHJlYXNvbmFibGUgdmFsdWVzIGJ1dCBzdGlsbCByYW5kb20pLi4KCgpOb3cgd2UgY2FuIHVzZSB0aGUgYGNvbGxlY3RfbWV0cmljcygpYCBmdW5jdGlvbiBhZ2FpbiB0byB0YWtlIGEgbG9vayBhdCB3aGF0IGhhcHBlbmVkIHdpdGggb3VyIGNyb3NzIHZhbGlkYXRpb24gdGVzdHMuIFdlIGNhbiBzZWUgdGhlIGRpZmZlcmVudCB2YWx1ZXMgY2hvc2VuIGZvciBgbXRyeWAgYW5kIGBtaW5fbmAgYW5kIHRoZSBtZWFuIHJtc2UgYW5kIHJzcSB2YWx1ZXMgYWNyb3NzIHRoZSBjcm9zcyB2YWxpZGF0aW9uIHNhbXBsZXMuCgpgYGB7cn0KdHVuZV9SRl9yZXN1bHRzJT4lCiAgY29sbGVjdF9tZXRyaWNzKCkKYGBgCgpXZSBjYW4gbm93IHVzZSB0aGUgYHNob3dfYmVzdCgpYCBmdW5jdGlvbiBhcyBpdCB3YXMgdHJ1bHkgaW50ZW5kZWQsIHRvIHNlZSB3aGF0IHZhbHVlcyBmb3IgYG1pbl9uYCBhbmQgYG10cnlgIHJlc3VsdGVkIGluIHRoZSBiZXN0IHBlcmZvcm1hbmNlLgoKYGBge3J9CnNob3dfYmVzdCh0dW5lX1JGX3Jlc3VsdHMsIG1ldHJpYyA9ICJybXNlIiwgbiA9MSkKYGBgClRoZXJlIHdlIGhhdmUgaXQuLi4gbG9va3MgbGlrZSBhbiBgbXRyeWAgb2YgMTcgYW5kIGBtaW5fbmAgb2YgNCBoYWQgdGhlIGJlc3QgYHJtc2VgIHZhbHVlLiBZb3UgY2FuIHZlcmlmeSB0aGlzIGluIHRoZSBhYm92ZSBvdXRwdXQsIGJ1dCBpdCBpcyBlYXNpZXIgdG8ganVzdCBwdWxsIHRoaXMgcm93IG91dCB1c2luZyB0aGlzIGZ1bmN0aW9uLiBXZSBjYW4gc2VlIHRoYXQgdGhlIG1lYW4gYHJtc2VgIHZhbHVlIGFjcm9zcyB0aGUgY3Jvc3MgdmFsaWRhdGlvbiBzZXRzIHdhcyAxLjcyMC4gQmVmb3JlIHR1bmluZyBpdCB3YXMgMS43MjUgIHdpdGggYSBzaW1pbGFyIGBzdGRfZXJyYCBzbyB0aGUgcGVyZm9ybWFuY2Ugd2FzIHZlcnkgc2xpZ2h0bHkgaW1wcm92ZWQuCgoKIyMgRmluYWwgbW9kZWwgcGVyZm9ybWFuY2UgZXZhbHVhdGlvbgoKTm93IHRoYXQgd2UgaGF2ZSBkZWNpZGVkIHRoYXQgd2UgaGF2ZSByZWFzb25hYmxlIHBlcmZvcm1hbmNlIHdpdGggb3VyIHRyYWluaW5nIGRhdGEsIHdlIGNhbiBzdG9wIGJ1aWxkaW5nIG91ciBtb2RlbCBhbmQgZXZhbHVhdGUgcGVyZm9ybWFuY2Ugd2l0aCBvdXIgdGVzdGluZyBkYXRhLiAKCkhlcmUsIHdlIHdpbGwgdXNlIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsIHRoYXQgd2UgYnVpbHQgdG8gcHJlZGljdCB2YWx1ZXMgZm9yIHRoZSBtb25pdG9ycyBpbiB0aGUgdGVzdGluZyBkYXRhIGFuZCB3ZSB3aWxsIHVzZSB0aGUgdmFsdWVzIGZvciBgbXRyeWAgYW5kIGBtaW5fbmAgdGhhdCB3ZSBqdXN0IGRldGVybWluZWQgYmFzZWQgb24gb3VyIHR1bmluZyBhbmFseXNpcyB0byBhY2hpZXZlIHRoZSBiZXN0IHBlcmZvcm1hbmNlLgoKU28sIGZpcnN0IHdlIG5lZWQgdG8gc3BlY2lmeSB0aGVzZSB2YWx1ZXMgaW4gYSB3b3JrZmxvdy4gV2UgY2FuIHVzZSB0aGUgYHNlbGVjdF9iZXN0KClgIGZ1bmN0aW9uIG9mIHRoZSBgdHVuZWAgcGFja2FnZSB0byBncmFiIHRoZSB2YWx1ZXMgdGhhdCB3ZXJlIGRldGVybWluZWQgdG8gYmUgYmVzdCBmb3IgYG10cnlgIGFuZCBgbWluX25gLgoKCgpgYGB7cn0KCnR1bmVkX1JGX3ZhbHVlczwtIHNlbGVjdF9iZXN0KHR1bmVfUkZfcmVzdWx0cywgInJtc2UiKQp0dW5lZF9SRl92YWx1ZXMKYGBgCgpOb3cgd2UgY2FuIGZpbmFsaXplIHRoZSBtb2RlbC93b3JrZmxvdyB0aGF0IHdlIHdlIHVzZWQgZm9yIHR1bmluZyB3aXRoIHRoZXNlIHZhbHVlcy4KCgpgYGB7cn0KUkZfdHVuZWRfd2Zsb3cgPC1SRl90dW5lX3dmbG93ICU+JQogIHR1bmU6OmZpbmFsaXplX3dvcmtmbG93KHR1bmVkX1JGX3ZhbHVlcykKYGBgCgoKV2l0aCB0aGUgYHdvcmtmbG93c2AgcGFja2FnZSwgd2UgY2FuIHVzZSB0aGUgc3BsaXR0aW5nIGluZm9ybWF0aW9uIGZvciBvdXIgb3JpZ2luYWwgZGF0YSBgcG1fc3BsaXRgIHRvIGZpdCB0aGUgZmluYWwgbW9kZWwgb24gdGhlIGZ1bGwgdHJhaW5pbmcgc2V0IGFuZCBhbHNvIG9uIHRoZSB0ZXN0aW5nIGRhdGEgdXNpbmcgdGhlIGBsYXN0X2ZpdCgpYCBmdW5jdGlvbiBvZiB0aGUgYHR1bmVgIHBhY2thZ2UuIE5vIHByZS1wcm9jZXNzaW5nIHN0ZXBzIGFyZSByZXF1aXJlZC4KClRoZSByZXN1bHRzIHdpbGwgc2hvdyB0aGUgcGVyZm9ybWFuY2UgdXNpbmcgdGhlIHRlc3RpbmcgZGF0YS4KCgpgYGB7cn0Kb3ZlcmFsbGZpdCA8LXR1bmU6Omxhc3RfZml0KFJGX3R1bmVkX3dmbG93LCBwbV9zcGxpdCkKICMgb3IKb3ZlcmFsbGZpdCA8LVJGX3dmbG93ICU+JQogIHR1bmU6Omxhc3RfZml0KHBtX3NwbGl0KQpgYGAKClRoZSBgb3ZlcmFsbGZpdGAgb3V0cHV0IGhhcyBhIGxvdCBvZiByZWFsbHkgdXNlZnVsIGluZm9ybWF0aW9uIGFib3V0IGhvdyB0aGUgbW9kZWwsIHRoZSBkYXRhIHRlc3QgYW5kIHRyYWluaW5nIHNwbGl0LCBhbmQgdGhlIHByZWRpY3Rpb25zIGZvciB0aGUgdGVzdGluZyBkYXRhLgoKVG8gc2VlIHRoZSBwZXJmb3JtYW5jZSBvbiB0aGUgdGVzdCBkYXRhIHdlIGNhbiB1c2UgdGhlIGBjb2xsZWN0X21ldHJpY3MoKWAgZnVuY3Rpb24gbGlrZSB3ZSBkaWQgYmVmb3JlLgpgYGB7cn0KICBjb2xsZWN0X21ldHJpY3Mob3ZlcmFsbGZpdCkKIApgYGAKCkF3ZXNvbWUhIFdlIGNhbiBzZWUgdGhhdCBvdXIgcm1zZSBvZiAxLjQ0IGlzIHF1aXRlIHNpbWlsYXIgd2l0aCBvdXIgdGVzdGluZyBkYXRhIGNyb3NzIHZhbGlkYXRpb24gc2V0cy4gV2UgYWNoaWV2ZWQgcXVpdGUgZ29vZCBwZXJmb3JtYW5jZSwgd2hpY2ggc3VnZ2VzdHMgdGhhdCB3ZSB3b3VsZCBjb3VsZCBwcmVkaWN0IG90aGVyIGxvY2F0aW9ucyB3aXRoIG1vcmUgc3BhcnNlIG1vbml0b3JpbmcgYmFzZWQgb24gb3VyIHByZWRpY3RvcnMgd2l0aCByZWFzb25hYmxlIGFjY3VyYWN5LgoKTm93IGlmIHlvdSB3YW50ZWQgdG8gdGFrZSBhIGxvb2sgYXQgdGhlIHByZWRpY3RlZCB2YWx1ZXMgZm9yIHRoZSB0ZXN0IHNldCAodGhlIDI5MiByb3dzIHdpdGggcHJlZGljdGlvbnMgb3V0IG9mIHRoZSA4NzYgb3JpZ2luYWwgbW9uaXRvciB2YWx1ZXMpIHlvdSBjYW4gdXNlIHRoZSAgYGNvbGxlY3RfcHJlZGljdGlvbnMoKWAgZnVuY3Rpb24gb2YgdGhlIGB0dW5lKClgIHBhY2thZ2U6CgpgYGB7cn0KdGVzdF9wcmVkaWN0aW9ucyA8LWNvbGxlY3RfcHJlZGljdGlvbnMob3ZlcmFsbGZpdCkKYGBgCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQp0ZXN0X3ByZWRpY3Rpb25zCmBgYAoKIyMjIyB7LnNjcm9sbGFibGUgfQpgYGB7ciwgZWNobyA9RkFMU0V9CnRlc3RfcHJlZGljdGlvbnMgJT4lCiAgcHJpbnQobiA9IDFlMykKYGBgCgojIyMjCgpOaWNlIQoKIyAqKkRhdGEgVmlzdWFsaXphdGlvbioqCioqKgoKT3VyIG1haW4gcXVlc3Rpb24gZm9yIHRoaXMgY2FzZSBzdHVkeSB3YXM6ICAKCj4gQ2FuIHdlIHByZWRpY3QgYW5udWFsIGF2ZXJhZ2UgYWlyIHBvbGx1dGlvbiBjb25jZW50cmF0aW9ucyBhdCB0aGUgZ3JhbnVsYXJpdHkgb2YgemlwIGNvZGUgcmVnaW9uYWwgbGV2ZWxzIHVzaW5nIHByZWRpY3RvcnMgc3VjaCBhcyBkYXRhIGFib3V0IHBvcHVsYXRpb24gZGVuc2l0eSwgdXJiYW5pemF0aW9uLCByb2FkIGRlbnNpdHksIGFzIHdlbGwgYXMsIHNhdGVsbGl0ZSBwb2xsdXRpb24gZGF0YSBhbmQgY2hlbWljYWwgbW9kZWxpbmcgZGF0YT8KClRodXMgZmFyLCB3ZSBoYXZlIGJ1aWxkIGEgbWFjaGluZSBsZWFybmluZyAoTUwpIG1vZGVsIHRvIHByZWRpY3QgZmluZSBwYXJ0aWN1bGF0ZSBtYXR0ZXIgYWlyIHBvbGx1dGlvbiBsZXZlbHMgYmFzZWQgb24gb3VyIHByZWRpY3RvciB2YXJpYWJsZXMgKG9yIGZlYXR1cmVzKS4KCk5vdywgbGV0J3MgbWFrZSBhIHBsb3Qgb2Ygb3VyIHByZWRpY3RlZCBvdXRjb21lIHZhbHVlcyAoJFxoYXR7WX0kKSBhbmQgYWN0dWFsIG91dGNvbWUgdmFsdWVzICRZJCB3ZSBvYnNlcnZlZC4gCgpGaXJzdCwgbGV0J3Mgc3RhcnQgYnkgbWFraW5nIGEgcGxvdCBvZiBvdXIgbW9uaXRvcnMuIApUbyBkbyB0aGlzLCB3ZSB3aWxsIHVzZSB0aGUgZm9sbG93aW5nIHBhY2thZ2VzIHRvIGNyZWF0ZSBhIG1hcCBvZiB0aGUgVVM6CgoxLiBgc2ZgIC0gdGhlIHNpbXBsZSBmZWF0dXJlcyBwYWNrYWdlIGhlbHBzIHRvIGNvbnZlcnQgZ2VvZ3JhcGhpY2FsIGNvb3JkaW5hdGVzIGludG8gYGdlb21ldHJ5YCB2YXJpYWJsZXMgd2hpY2ggYXJlIHVzZWZ1bCBmb3IgbWFraW5nIDJEIHBsb3RzCjIuIGBtYXBzYCAtIHRoaXMgcGFja2FnZSBjb250YWlucyBnZW9ncmFwaGljYWwgb3V0bGluZXMgYW5kIHBsb3R0aW5nIGZ1bmN0aW9ucyB0byBjcmVhdGUgcGxvdHMgd2l0aCBtYXBzIAozLiBgcm5hdHVyYWxlYXJ0aGAtIHRoaXMgYWxsb3dzIGZvciBlYXN5IGludGVyYWN0aW9uIHdpdGggbWFwIGRhdGEgZnJvbSBbTmF0dXJhbCBFYXJ0aF0oaHR0cDovL3d3dy5uYXR1cmFsZWFydGhkYXRhLmNvbS8pIHdoaWNoIGlzIGEgcHVibGljIGRvbWFpbiBtYXAgZGF0YXNldAo0LiBgcmdlb3NgIC0gdGhpcyBwYWNrYWdlIGludGVyZmFjZXMgd2l0aCB0aGUgR2VvbWV0cnkgRW5naW5lLU9wZW4gU291cmNlIChgR0VPU2ApIHdoaWNoIGlzIGFsc28gaGVscGZ1bCBmb3IgY29vcmRpbmF0ZSBjb252ZXJzaW9uCgpXZSB3aWxsIHN0YXJ0IHdpdGggZ2V0dGluZyBhbiBvdXRsaW5lIG9mIHRoZSBVUyB3aXRoIHRoZSBgbmVfY291bnRyaWVzKClgIGZ1bmN0aW9uIG9mIHRoZSBgcm5hdHVyYWxlYXJ0aGAgcGFja2FnZSB3aGljaCB3aWxsIHJldHVybiBwb2x5Z29ucyBvZiB0aGUgY291bnRyaWVzIGluIHRoZSBbTmF0dXJhbCBFYXJ0aF0oaHR0cDovL3d3dy5uYXR1cmFsZWFydGhkYXRhLmNvbS8pIGRhdGFzZXQuCgpBVk9DQURPOiBTbyBJIGRlZmluaXRlbHkgaGF2ZSB0aGUgYHJuYXR1cmFsZWFydGhkYXRhYCBwYWNrYWdlIGluc3RhbGxlZCwgYnV0IGZvciBzb21lb25lIHJlYXNvbiBldmVyeSB0aW1lIEkgZ28gdG8gcnVuIHRoZSBjb2RlIGJlbG93IGl0IHNheXMgdGhhdCBJIG5lZWQgdG8gaW5zdGFsbCB0aGUgcGFja2FnZS4gSnVzdCBjdXJpb3VzIC0tIHdoYXQgdmVyc2lvbiBvZiBSIGFyZSB5b3UgdXNpbmc/YXZvY2FkbyByZXNwb25zZSAtIHRoZSBlbmQgb2YgdGhlIGNhc2Ugc3R1ZHkgc2hvd3MgdGhpcy4uIGhvcGVmdWxseSB0aGlzIHdvcmtzIG5vdyA6LyBwbGVhc2UgbGV0IG1lIGtub3cgYW5kIEkgd2lsbCBmaWd1cmUgaXQgb3V0CgpgYGB7cn0KCndvcmxkIDwtIG5lX2NvdW50cmllcyhzY2FsZSA9ICJtZWRpdW0iLCByZXR1cm5jbGFzcyA9ICJzZiIpCmdsaW1wc2Uod29ybGQpCgpgYGAKCgpIZXJlIHlvdSBjYW4gc2VlIHRoZSBkYXRhIGFib3V0IHRoZSBjb3VudHJpZXMgaW4gdGhlIHdvcmxkLiBOb3RpY2UgdGhlIGBnZW9tZXRyeWAgdmFyaWFibGUuIFRoaXMgaXMgdXNlZCB0byBjcmVhdGUgdGhlIG91dGxpbmVzIHRoYXQgd2Ugd2FudC4gCgpOb3cgd2UgY2FuIHVzZSB0aGUgYGdlb21fc2YoKWAgZnVuY3Rpb24gb2YgdGhlIGBnZ3Bsb3QyYCBwYWNrYWdlIHRvIGNyZWF0ZSBhIHZpc3VhbCBvZiBzaW1wbGUgZmVhdHVyZSAodGhlIGdlb21ldHJ5IGNvb3JkaW5hdGVzIGZvdW5kIGluIHRoZSBgZ2VvbWV0cnlgIHZhcmlhYmxlKS4KCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHdvcmxkKSArCiAgICBnZW9tX3NmKCkgCgpgYGAKClNvIG5vdyB3ZSBjYW4gc2VlIHRoYXQgd2UgaGF2ZSBvdXRsaW5lcyBvZiBhbGwgdGhlIGNvdW50cmllcyBpbiB0aGUgd29ybGQuCgpXZSB3YW50IHRvIGxpbWl0IHRoaXMganVzdCB0byB0aGUgY29vcmRpbmF0ZXMgZm9yIHRoZSBVUy4gV2Ugd2lsbCBkbyB0aGlzIGJhc2VkIG9uIHRoZSBjb29yZGluYXRlcyB3ZSBmb3VuZCBvbiBXaWtpcGVkaWEuIEFjY29yZGluZyB0byB0aGlzIFtsaW5rXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXN0X29mX2V4dHJlbWVfcG9pbnRzX29mX3RoZV9Vbml0ZWRfU3RhdGVzI1dlc3Rlcm5tb3N0KXt0YXJnZXQ9Il9ibGFuayJ9LCB0aGVzZSBhcmUgdGhlIGxhdGl0dWRlIGFuZCBsb25naXR1ZGUgYm91bmRzIG9mIHRoZSBjb250aW5lbnRhbCBVUzoKCi0gdG9wID0gNDkuMzQ1Nzg2OCAjIG5vcnRoIGxhdAotIGxlZnQgPSAtMTI0Ljc4NDQwNzkgIyB3ZXN0IGxvbmcKLSByaWdodCA9IC02Ni45NTEzODEyICMgZWFzdCBsb25nCi0gYm90dG9tID0gIDI0Ljc0MzMxOTUgIyBzb3V0aCBsYXQKCmBgYHtyfQoKZ2dwbG90KGRhdGEgPSB3b3JsZCkgKwogICAgZ2VvbV9zZigpICsKICAgIGNvb3JkX3NmKHhsaW0gPSBjKC0xMjUsIC02NiksIHlsaW0gPSBjKDI0LjUsIDUwKSwgCiAgICAgICAgICAgICBleHBhbmQgPSBGQUxTRSkKYGBgCk5vdyB3ZSBqdXN0IGhhdmUgYSBwbG90IHRoYXQgaXMgbW9zdGx5IGxpbWl0ZWQgdG8gdGhlIG91dGxpbmUgb2YgdGhlIFVTLgoKTm93IHdlIHdpbGwgdXNlIHRoZSBgZ2VvbV9wb2ludCgpYCBmdW5jdGlvbiBvZiB0aGUgYGdncGxvdGAgcGFja2FnZSB0byBhZGQgc2NhdHRlciBwbG90IG9uIHRvcCBvZiB0aGUgbWFwLiBXZSB3YW50IHRvIHNob3cgd2hlcmUgdGhlIG1vbml0b3JzIGFyZSBsb2NhdGVkIGJhc2VkIG9uIHRoZSBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIHZhbHVlcyBpbiB0aGUgZGF0YS4KCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHdvcmxkKSArCiAgICBnZW9tX3NmKCkgKwogICAgY29vcmRfc2YoeGxpbSA9IGMoLTEyNSwgLTY2KSwgeWxpbSA9IGMoMjQuNSwgNTApLCAKICAgICAgICAgICAgIGV4cGFuZCA9IEZBTFNFKSsKICAgIGdlb21fcG9pbnQoZGF0YSA9IHBtLCBhZXMoeCA9IGxvbiwgeSA9IGxhdCksIHNpemUgPSAyLCAKICAgICAgICAgICAgICAgc2hhcGUgPSAyMywgZmlsbCA9ICJkYXJrcmVkIikKCmBgYApOaWNlIQoKTm93IGxldCdzIGFkZCBjb3VudHkgbGluZXMuCgpDb3VudHkgZ3JhcGhpY2FsIGRhdGEgaXMgYXZhaWxhYmxlIGZyb20gdGhlIGBtYXBzYCBwYWNrYWdlLiAKVGhlIGBzZmAgcGFja2FnZSB3aGljaCBhZ2FpbiBpcyBzaG9ydCBmb3Igc2ltcGxlIGZlYXR1cmVzIGNyZWF0ZXMgYSBkYXRhIGZyYW1lIGFib3V0IHRoaXMgZ3JhcGhpY2FsIGRhdGEgc28gdGhhdCB3ZSBjYW4gd29yayB3aXRoIGl0LgoKYGBge3J9CmNvdW50aWVzIDwtIAogIHNmOjpzdF9hc19zZihtYXBzOjptYXAoImNvdW50eSIsIHBsb3QgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gVFJVRSkpCgpjb3VudGllcwpgYGAKCk5vdyB3ZSB3aWxsIHVzZSB0aGlzIGRhdGEgd2l0aGluIHRoZSBgZ2VvbV9zZigpYCBmdW5jdGlvbiB0byBhZGQgdGhpcyB0byBvdXIgcGxvdC4gIFdlIHdpbGwgYWxzbyBhZGQgYSB0aXRsZSB1c2luZyB0aGUgYGdndGl0bGUoKWAgZnVuY3Rpb24sIGFzIHdlbGwgYXMgcmVtb3ZlIGF4aXMgdGlja3MgYW5kIHRpdGxlcyB1c2luZyB0aGUgYHRoZW1lKClgIGZ1bmN0aW9uIG9mIHRoZSBgZ2dwbG90MmAgcGFja2FnZS4KCmBgYHtyfQptb25pdG9ycyA8LSBnZ3Bsb3QoZGF0YSA9IHdvcmxkKSArCiAgICBnZW9tX3NmKGRhdGEgPSBjb3VudGllcywgZmlsbCA9IE5BLCBjb2xvciA9IGdyYXkoLjUpKSsKICAgICAgY29vcmRfc2YoeGxpbSA9IGMoLTEyNSwgLTY2KSwgeWxpbSA9IGMoMjQuNSwgNTApLCAKICAgICAgICAgICAgIGV4cGFuZCA9IEZBTFNFKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBwbSwgYWVzKHggPSBsb24sIHkgPSBsYXQpLCBzaXplID0gMiwgCiAgICAgICAgICAgICAgIHNoYXBlID0gMjMsIGZpbGwgPSAiZGFya3JlZCIpICsKICAgIGdndGl0bGUoIk1vbml0b3IgTG9jYXRpb25zIikgKwogICAgdGhlbWUoYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpKQoKbW9uaXRvcnMKYGBgCgpHcmVhdCEKCk5vdywgbGV0J3MgYWRkIGEgZmlsbCBhdCB0aGUgY291bnR5LWxldmVsIGZvciB0aGUgdHJ1ZSBtb25pdG9yIHZhbHVlcyBvZiBhaXIgcG9sbHV0aW9uLgoKRmlyc3QsIHdlIG5lZWQgdG8gZ2V0IHRoZSBjb3VudHkgbWFwIGRhdGEgdGhhdCB3ZSBqdXN0IGdvdCBhbmQgb3VyIGFpciBwb2xsdXRpb24gZGF0YSB0byBoYXZlIHNpbWlsYXJseSBmb3JtYXR0ZWQgY291bnR5IG5hbWVzIHNvIHRoYXQgd2UgY2FuIGNvbWJpbmUgdGhlIGRhdGFzZXRzIHRvZ2V0aGVyLgoKV2UgY2FuIHNlZSB0aGF0IGluIHRoZSBgY291bnR5YCBkYXRhIHRoZSBjb3VudGllcyBhcmUgbGlzdGVkIGFmdGVyIHRoZSBzdGF0ZSBuYW1lIGFuZCBhIGNvbW1hLiBJbiBhZGRpdGlvbiB0aGV5IGFyZSBhbGwgbG93ZXIgY2FzZS4KCgpgYGB7cn0KaGVhZChjb3VudGllcykKYGBgCgpJbiBjb250cmFzdCwgb3VyIGFpciBwb2xsdXRpb24gYHBtYCBkYXRhIHNob3dzIGNvdW50aWVzIGFzIHRpdGxlcyB3aXRoIHRoZSBmaXJzdCBsZXR0ZXIgYXMgdXBwZXIgY2FzZS4gCgpgYGB7cn0KZHBseXI6OnB1bGwocG0sIGNvdW50eSkgJT4lCiAgaGVhZCgpCmBgYAoKV2UgY2FuIHVzZSB0aGUgYHNlcGFyYXRlKClgIGZ1bmN0aW9uIG9mIHRoZSBgdGlkeXJgIHBhY2thZ2UgdG8gc2VwYXJhdGUgdGhlIGBJRGAgdmFyaWFibGUgb2Ygb3VyIGBjb3VudGllc2AgZGF0YSBpbnRvIHR3byBgdmFyaWFibGVzYCBiYXNlZCBvbiB0aGUgY29tbWEgYXMgYSBzZXBhcmF0b3IuCgpgYGB7cn0KY291bnRpZXMgJTw+JSAKICB0aWR5cjo6c2VwYXJhdGUoSUQsIGludG8gPSBjKCJzdGF0ZSIsICJjb3VudHkiKSwgc2VwID0gIiwiKQoKaGVhZChjb3VudGllcykKYGBgCk5vdyB3ZSBqdXN0IG5lZWQgdG8gbWFrZSB0aGVzZSBuYW1lcyBpbiB0aGUgbmV3IGBjb3VudHlgIHZhcmlhYmxlIG9mIHRoZSBgY291bnRpZXNgIGRhdGEgdG8gYmUgaW4gdGl0bGUgZm9ybWF0LiBXZSBjYW4gdXNlIHRoZSBgc3RyX3RvX3RpdGxlKClgIGZ1bmN0aW9uIG9mIHRoZSBgc3RyaW5ncmAgcGFja2FnZSB0byBkbyB0aGlzLiAKYGBge3J9CmNvdW50aWVzW1siY291bnR5Il1dIDwtIHN0cmluZ3I6OnN0cl90b190aXRsZShjb3VudGllc1tbImNvdW50eSJdXSkKYGBgCgpHcmVhdCEgTm93IHRoZSBjb3VudHkgaW5mb3JtYXRpb24gaXMgdGhlIHNhbWUgZm9yIHRoZSBgY291bnRpZXNgIGFuZCBgcG1gIGRhdGEuCgpXZSBjYW4gdXNlIHRoZSBgaW5uZXJfam9pbigpYCBmdW5jdGlvbiBvZiB0aGUgYGRwbHlyYCBwYWNrYWdlIHRvIGpvaW4gdGhlIGRhdGFzZXRzIHRvZ2V0aGVyIGJhc2VkIG9uIHRoZSBgY291bnR5YCB2YXJpYWJsZXMgaW4gZWFjaC4gVGhpcyBmdW5jdGlvbiB3aWxsIGtlZXAgYWxsIHJvd3MgdGhhdCBhcmUgaW4gYm90aCBkYXRhc2V0cy4KCmBgYHtyfQptYXBfZGF0YSA8LWRwbHlyOjppbm5lcl9qb2luKGNvdW50aWVzLCBwbSwgYnkgPSAiY291bnR5IikKCmdsaW1wc2UobWFwX2RhdGEpCgpgYGAKTmljZSEgd2UgY2FuIHNlZSB0aGF0IHdlIGhhdmUgYWRkIGEgYGdlb21gIHZhcmlhYmxlIHRvIHRoZSBgcG1gIGRhdGEuCgpOb3cgd2UgY2FuIHVzZSB0aGlzIHRvIGNvbG9yIHRoZSBjb3VudGllcyBpbiBvdXIgcGxvdCBiYXNlZCBvbiB0aGUgYHZhbHVlYCB2YXJpYWJsZSBvZiBvdXIgYHBtYCBkYXRhLCB3aGljaCB5b3UgbWF5IHJlY2FsbCBpcyB0aGUgYWN0dWFsIG1vbml0b3IgZGF0YSBmb3IgZmluZSBwYXJ0aWN1bGF0ZSBhaXIgcG9sbHV0aW9uIGF0IGVhY2ggbW9uaXRvci4gCgpXRSBjYW4gZG8gc28gdXNpbmcgdGhlIGBzY2FsZV9maWxsX2dyYWRpZW50bigpYCBmdW5jdGlvbiBvZiB0aGUgYGdncGxvdDJgIHBhY2thZ2Ugd2hpY2ggY3JlYXRlcyBjb2xvciBncmFkaWVudCBiYXNlZCBvbiBhIHZhcmlhYmxlLiBJbiB0aGlzIGNhc2UgaXQgaXMgdGhlIHZhcmlhYmxlIHRoYXQgd2FzIHNwZWNpZmllZCBhcyB0aGUgYGZpbGxgIGluIHRoZSBgYWVzYCBmdW5jdGlvbiBvZiB0aGUgYGdlb21fc2YoKWAgZnVuY3Rpb24uIFdlIHNwZWNpZmllZCB0aGF0IGl0IHdvdWxkIGJlIHRoZSBgdmFsdWVgIHZhcmlhYmxlIG9mIHRoZSBgcG1gIGRhdGEuCgpUaGlzIGBzY2FsZV9maWxsX2dyYWRpZW50bigpYCBmdW5jdGlvbiAgYWxzbyBhbGxvd3MgeW91IHRvIHNwZWNpZnkgdGhlIGNvbG9ycywgd2hhdCB0byBkbyBhYm91dCBOQSB2YWx1ZXMgKHNob3VsZCB0aGV5IGJlIGEgc3BlY2lmaWMgY29sb3Igb3IgdHJhbnNwYXJlbnQpIGFuZCB0aGUgYnJlYWtzLCBsaW1pdHMsIGxhYmVscyBhbmQgbmFtZS90aXRsZSBvbiB0aGUgbGVnZW5kIGZvciB0aGUgY29sb3IgZ3JhZGllbnQuIAoKYGBge3J9Cgp0cnV0aCA8LWdncGxvdChkYXRhID0gd29ybGQpICsKICBjb29yZF9zZih4bGltID0gYygtMTI1LCAtNjYpLCB5bGltID0gYygyNC41LCA1MCksIGV4cGFuZCA9IEZBTFNFKSsKICAgIGdlb21fc2YoZGF0YSA9IG1hcF9kYXRhLCBhZXMoZmlsbCA9IHZhbHVlKSkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnM9dG9wby5jb2xvcnMoNyksIG5hLnZhbHVlID0gInRyYW5zcGFyZW50IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzPWMoMCwxMCwyMCksbGFiZWxzPWMoMCwxMCwyMCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbWl0cz1jKDAsMjMuNSksIG5hbWUgPSAiUE0gdWcvbTMiKSArCiAgZ2d0aXRsZSgiVHJ1ZSBQTSAyLjUgbGV2ZWxzIikgKwogICAgdGhlbWUoYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpKQoKdHJ1dGgKCmBgYApOaWNlIQoKTm93IGxldCdzIGRvIHRoZSBzYW1lIHdpdGggb3VyIHByZWRpY3RlZCBvdXRjb21lIHZhbHVlcy4KCkxldCdzIGdyYWIgYm90aCB0aGUgdGVzdGluZyBhbmQgdHJhaW5pbmcgcHJlZGljdGVkIG91dGNvbWUgdmFsdWVzIHNvIHRoYXQgd2UgaGF2ZSBhcyBtdWNoIGRhdGEgYXMgcG9zc2libGUuIAoKRmlyc3Qgd2UgbmVlZCB0byBmaXQgb3VyIHRyYWluaW5nIGRhdGEgd2l0aCBvdXIgZmluYWwgbW9kZWwgdG8gYmUgYWJsZSB0byBnZXQgdGhlIHByZWRpY3Rpb25zIGZvciB0aGUgbW9uaXRvcnMgaW5jbHVkZWQgaW4gdGhlIHRyYWluaW5nIHNldC4gV2UgZGlkIHRoaXMgdXNpbmcgdGhlIGBsYXN0X2ZpdCgpYCBmdW5jdGlvbiwgYnV0IHRoZSBvdXRwdXQgb2YgdGhpcyBtYWtlcyBpdCBkaWZmaWN1bHQgdG8gZ3JhYiB0aGUgcHJlZGljdGVkIHZhbHVlcyBmb3IgdGhlIHRyYWluaW5nIGRhdGEsIGFuZCBpdCBpcyBhbHNvIGRpZmZpY3VsdCB0byBnZXQgdGhlIGlkIHZhcmlhYmxlcyBmb3IgdGhlIHRlc3RpbmcgZGF0YS4gCgoKVGh1cyB3ZSB3aWxsIHVzZSB0aGUgcGFyc25pcCBgZml0KClgIGFuZCAgYHByZWRpY3QoKWAgZnVuY3Rpb25zIHRvIGRvIHRoaXMgb3V0c2lkZSBvZiB0aGUgYHdvcmtmbG93c2AgcGFja2FnZSBsaWtlIHNvOgoKIyMjIyB7LnRoaW5rX3F1ZXN0aW9uX2Jsb2NrfQo8Yj48dT4gUXVlc3Rpb24gT3Bwb3J0dW5pdHkgPC91PjwvYj4KCldoeSBkbyB3ZSBub3QgbmVlZCBwcmUtcHJvY2Vzc2VkIGRhdGE/CgojIyMjCgoqKioKCjxkZXRhaWxzPiA8c3VtbWFyeT4gQ2xpY2sgaGVyZSB0byByZXZlYWwgdGhlIGFuc3dlci4gPC9zdW1tYXJ5PgoKU2luY2Ugd2UgYXJlIHVzaW5nIGEgd29ya2Zsb3csIHRoZSBkYXRhIHdpbGwgYmUgcHJlLXByb2Nlc3NlZCB3aGVuIGl0IGlzIGZpdCBhcyB3ZWxsLgoKPC9kZXRhaWxzPgoqKioKCgpgYGB7cn0KClJGX2ZpbmFsX3RyYWluX2ZpdCA8LSBwYXJzbmlwOjpmaXQoUkZfdHVuZWRfd2Zsb3csIGRhdGEgPSB0cmFpbl9wbSkKUkZfZmluYWxfdGVzdF9maXQgPC0gcGFyc25pcDo6Zml0KFJGX3R1bmVkX3dmbG93LCBkYXRhID0gdGVzdF9wbSkKCgp2YWx1ZXNfcHJlZF90cmFpbiA8LSAKICBwcmVkaWN0KFJGX2ZpbmFsX3RyYWluX2ZpdCwgdHJhaW5fcG0pICU+JSAKICBiaW5kX2NvbHModHJhaW5fcG0gJT4lIHNlbGVjdCh2YWx1ZSwgZmlwcywgY291bnR5LCBpZCkpIAoKdmFsdWVzX3ByZWRfdHJhaW4KCnZhbHVlc19wcmVkX3Rlc3QgPC0gCiAgcHJlZGljdChSRl9maW5hbF90ZXN0X2ZpdCwgdGVzdF9wbSkgJT4lIAogIGJpbmRfY29scyh0ZXN0X3BtICU+JSBzZWxlY3QodmFsdWUsIGZpcHMsIGNvdW50eSwgaWQpKSAKCnZhbHVlc19wcmVkX3Rlc3QKYGBgCgpOb3cgd2UgY2FuIGNvbWJpbmUgdGhpcyBkYXRhIGZvciB0aGUgcHJlZGljdGlvbnMgZm9yIGFsbCBtb25pdG9ycyB1c2luZyB0aGUgYGJpbmRfcm93cygpYCBmdW5jdGlvbiBvZiB0aGUgYGRwbHlyYCBwYWNrYWdlLCB3aGljaCB3aWxsIGVzc2VudGlhbGx5IGFwcGVuZCB0aGUgc2Vjb25kIGRhdGFzZXQgdG8gdGhlIGZpcnN0LgoKYGBge3J9CmFsbF9wcmVkIDwtIGJpbmRfcm93cyh2YWx1ZXNfcHJlZF90ZXN0LCB2YWx1ZXNfcHJlZF90cmFpbikKCmFsbF9wcmVkCmBgYAoKR3JlYXQhIGFzIHdlIGNhbiBzZWUgdGhlcmUgYXJlIDg3NiB2YWx1ZXMgbGlrZSB3ZSB3b3VsZCBleHBlY3QgZm9yIGFsbCBvZiB0aGUgbW9uaXRvcnMuIFdlIGNhbiB1c2UgdGhlIGBjb3VudHlgIHZhcmlhYmxlIHRvIGNvbWJpbmUgdGhpcyB3aXRoIHRoZSBgY291bnRpZXNgIGRhdGEgbGlrZSB3ZSBkaWQgd2l0aCB0aGUgYHBtYCBkYXRhIHByZXZpb3VzbHkgc28gdGhhdCB3ZSBjYW4gdXNlIHRoZSBgdmFsdWVgIHZhcmlhYmxlIGFzIGEgY29sb3Igc2NoZW1lIGZvciBvdXIgbWFwLgoKCmBgYHtyfQptYXBfZGF0YSA8LSBpbm5lcl9qb2luKGNvdW50aWVzLCBhbGxfcHJlZCwgYnkgPSAiY291bnR5IikKCnByZWQgPC0gZ2dwbG90KGRhdGEgPSB3b3JsZCkgKwogICAgICAgICAgY29vcmRfc2YoeGxpbSA9IGMoLTEyNSwgLTY2KSwgeWxpbSA9IGMoMjQuNSwgNTApLCAKICAgICAgICAgICAgICAgICBleHBhbmQgPSBGQUxTRSkgKwogICAgZ2VvbV9zZihkYXRhID0gbWFwX2RhdGEsIGFlcyhmaWxsID0gLnByZWQpKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3Vycz10b3BvLmNvbG9ycyg3KSwgbmEudmFsdWUgPSAidHJhbnNwYXJlbnQiLAogICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcz1jKDAsMTAsMjApLGxhYmVscz1jKDAsMTAsMjApLAogICAgICAgICAgICAgICAgICAgICAgIGxpbWl0cz1jKDAsMjMuNSksIG5hbWUgPSAiUE0gdWcvbTMiKSArCiAgZ2d0aXRsZSgiUHJlZGljdGVkIFBNIDIuNSBsZXZlbHMiKSsKICAgIHRoZW1lKGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy55PWVsZW1lbnRfYmxhbmsoKSkKCnByZWQKYGBgCgpOb3cgd2Ugd2lsbCB1c2UgdGhlIGBwYXRjaHdvcmtgIHBhY2thZ2UgdG8gY29tYmluZSBvdXIgbGFzdCB0d28gcGxvdHMuIFRoaXMgYWxsb3dzIHVzIHRvIGNvbWJpbmUgcGxvdHMgdXNpbmcgdGhlIGArYCBvciB0aGUgYC9gIC4gVGhlIGArYCB3aWxsIHBsYWNlIHBsb3RzIHNpZGUgYnkgc2lkZSBhbmQgdGhlIGAvYCB3aWxsIHBsYWNlIHBsb3RzIHRvcCB0byBib3R0b20uCgoKTm93IGxldCdzIGp1c3QgY29tYmluZSB0aGUgdHJ1dGggcGxvdCBhbmQgdGhlIHByZWRpY3Rpb24gcGxvdHMgdG9nZXRoZXI6CmBgYHtyfQp0cnV0aC9wcmVkCgpgYGAKCldlIGNhbiBzZWUgdGhhdCB0aGUgcHJlZGljdGVkIGZpbmUgcGFydGljbGUgYWlyIHBvbGx1dGlvbiB2YWx1ZXMgaW4gKHVnL20zKSBhcmUgcXVpdGUgc2ltaWxhciB0byB0aGUgdHJ1ZSB2YWx1ZXMgbWVhc3VyZWQgYnkgdGhlIGFjdHVhbCBncmF2aW1ldHJpYyBtb25pdG9ycy4gV2UgY2FuIGFsc28gc2VlIHRoYXQgc291dGhlcm4gQ2FsaWZvcm5pYSBoYXMgc29tZSBsYXJnZSBjb3VudGllcyB3aXRoIHdvcnNlIHBvbGx1dGlvbiAoYXMgdGhleSBhcmUgeWVsbG93IGFuZCB0aHVzIGhhdmUgbXVjaCBoaWdoZXIgcGFydGljdWxhdGUgbWF0dGVyIGxldmVscykuCgpMZXQncyBhZGQgc29tZSB0ZXh0IHRvIG91ciBwbG90IHRvIGV4cGxhaW4gaXQgYSBiaXQgbW9yZS4KCmBgYHtyfQp0cnV0aC9wcmVkICsgcGxvdF9hbm5vdGF0aW9uKHRpdGxlID0gIlJhbmRvbSBmb3Jlc3QgbW9kZWwgcHJlZGljdHMgZmluZSBwYXJ0aWN1bGF0ZSBtYXR0ZXIgKFBNIDIuNSkgXG5haXIgcG9sbHV0aW9uIGJhc2VkIG9uIGRhdGEgYWJvdXQgcm9hZCBwcm94aW1pdHksIHBvcHVsYXRpb24gZGVuc2l0eSxcbmFuZCBvdGhlciBwcmVkaWN0b3JzIHJlYXNvbmFibHkgd2VsbCIpCgpgYGAKCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBldmFsPUZBTFNFfQpwbmcoaGVyZTo6aGVyZSgiaW1nIiwgIm1haW5fcGxvdF9tYXBzLnBuZyIpLCAKICAgIGhlaWdodCA9IDE1MDAsIHdpZHRoID0gMjAwMCwgcmVzID0gMzAwKQp0cnV0aC9wcmVkICsgcGxvdF9hbm5vdGF0aW9uKHRpdGxlID0gIlJhbmRvbSBmb3Jlc3QgbW9kZWwgcHJlZGljdHMgZmluZSBwYXJ0aWN1bGF0ZSBtYXR0ZXIgKFBNIDIuNSkgXG5haXIgcG9sbHV0aW9uIGJhc2VkIG9uIGRhdGEgYWJvdXQgcm9hZCBwcm94aW1pdHksIHBvcHVsYXRpb24gZGVuc2l0eSxcbmFuZCBvdGhlciBwcmVkaWN0b3JzIHJlYXNvbmFibHkgd2VsbCIpCmRldi5vZmYoKQpgYGAKCiMgKipTdW1tYXJ5KioKKioqCgojIyBTeW5vcHNpcwoKSW4gdGhpcyBjYXNlIHN0dWR5LCB3ZSBleHBsb3JlZCBncmF2aW1ldHJpYyBtb25pdG9yaW5nIGRhdGEgb2YgZmluZSBwYXJ0aWN1bGF0ZSBtYXR0ZXIgYWlyIHBvbGx1dGlvbiAob3V0Y29tZSB2YXJpYWJsZSkuIApPdXIgZ29hbCB3YXMgdG8gYWJsZSB0byBwcmVkaWN0IGFpciBwb2xsdXRpb24gb24gbW9uaXRvcnMgd2hlcmUgd2Ugb25seSBoYWQgcHJlZGljdG9yIHZhcmlhYmxlcyAob3IgZmVhdHVyZXMpIHdpdGhvdXQgaGF2aW5nIG9ic2VydmVkIGEgY29ycmVzcG9uZGluZyBtZWFzdXJlbWVudCBvZiBhaXIgcG9sbHV0aW9uLgoKT3VyIGxlYXJuaW5nIG9iamVjdGl2ZXMgd2VyZTogCgotIEludHJvZHVjZSBjb25jZXB0cyBpbiBtYWNoaW5lIGxlYXJuaW5nCi0gRGVtb25zdHJhdGUgaG93IHRvIGJ1aWxkIGEgbWFjaGluZSBsZWFybmluZyBtb2RlbCB3aXRoIGB0aWR5bW9kZWxzYAotIERlbW9uc3RyYXRlIGhvdyB0byB2aXN1YWxpemUgZ2VvLXNwYXRpYWwgZGF0YSB1c2luZyBgZ2dwbG90MmAKClVzaW5nIHRoZSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscyBidWlsdCBpbiB0aGlzIGNhc2Ugc3R1ZHksIHdlIGNvdWxkIG5vdyBleHRlbmQgdGhpcyBtb2RlbCB0byBiZSB1c2VkIHRvIHByZWRpY3QgYWlyIHBvbGx1dGlvbiBsZXZlbHMgaW4gYXJlYXMgd2l0aCBwb29yIG1vbml0b3JpbmcsIHRvIGhlbHAgaWRlbnRpZnkgcmVnaW9ucyB3aGVyZSBwb3B1bGF0aW9ucyBtYXliZSBlc3BlY2lhbGx5IGF0IHJpc2sgZm9yIHRoZSBoZWFsdGggZWZmZWN0cyBvZiBhaXIgcG9sbHV0aW9uLiAgCgpBbmFseXNlcyBsaWtlIHRoZSBvbmUgaW4gb3VyIGNhc2Ugc3R1ZHkgYXJlIGltcG9ydGFudCBmb3IgZGVmaW5pbmcgd2hpY2ggZ3JvdXBzIGNvdWxkIGJlbmVmaXQgdGhlIG1vc3QgZnJvbSBpbnRlcnZlbnRpb25zLCBlZHVjYXRpb24sIGFuZCBwb2xpY3kgY2hhbmdlcyB3aGVuIGF0dGVtcHRpbmcgdG8gbWl0aWdhdGUgcHVibGljIGhlYWx0aCBjaGFsbGVuZ2VzLiBZb3UgY2FuIHNlZSBpbiB0aGlzIFthcnRpY2xlXShodHRwczovL3d3dy5uZWptLm9yZy9kb2kvZnVsbC8xMC4xMDU2L05FSk1vYTE3MDI3NDcpe3RhcmdldD0iX2JsYW5rIn0gdGhhdCBtYW55IGFkZGl0aW9uYWwgY29uc2lkZXJhdGlvbnMgd291bGQgYmUgaW52b2x2ZWQgdG8gYWRlcXVhdGVseSB1bmRlcnN0YW5kIHRoZSBkYXRhIGVub3VnaCB0byByZWNvbW1lbmQgcG9saWN5IGNoYW5nZXMuCgoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIGhlcmUgZm9yIG1vcmUgb24gd2hhdCB3ZSBsZWFybmVkIHdpdGggYHRpZHltb2RlbHNgIDwvc3VtbWFyeT4KCkhlcmUsIHdlIHByb3ZpZGUgYW4gb3ZlcnZpZXcgb2YgdGhlIGB0aWR5bW9kZWxzYCBmcmFtZXdvcmsuIAphdm9jYWRvLi4uIHR3byBvcHRpb25zOgoKYGBge3IsIGVjaG89RkFMU0UsIG91dC53aWR0aD0iODAwcHgifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWciLCJlY29zeXN0ZW0ucG5nIikpCmBgYAoKCmBgYHtyLCBlY2hvPUZBTFNFLCBvdXQud2lkdGg9IjgwMHB4In0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwibmV3X3RpZHltb2RlbHNfZWNvc3lzdGVtLnBuZyIpKQpgYGAKCldlIHBlcmZvcm1lZCB0aGUgbWFqb3Igc3RlcHMgb2YgbWFjaGluZSBsZWFybmluZyB0aGF0IHdlIGludHJvZHVjZWQgaW4gdGhlIGJlZ2lubmluZyBvZiB0aGUgZGF0YSBhbmFseXNpczogIAoKMS4gRGF0YSBleHBsb3JhdGlvbiAgCgpXZSB1c2VkIHBhY2thZ2VzIGxpa2UgYHNraW1yYCwgYHN1bW1hcnl0b29sc2AsIGBjb3JycGxvdGAsIGFuZCBgR0dhbGx5YCB0byBiZXR0ZXIgdW5kZXJzdGFuZCBvdXIgZGF0YS4gVGhlc2UgcGFja2FnZXMgZ2F2ZSBjYW4gdGVsbCB1cyBob3cgbWFueSBtaXNzaW5nIHZhbHVlcyBlYWNoIHZhcmlhYmxlIGhhcyAoaWYgYW55KSwgdGhlIGNsYXNzIG9mIGVhY2ggdmFyaWFibGUsIHRoZSBkaXN0cmlidXRpb24gb2YgdmFsdWVzIGZvciBlYWNoIHZhcmlhYmxlLCB0aGUgc3BhcnNpdHkgb2YgZWFjaCB2YXJpYWJsZSwgYW5kIHRoZSBsZXZlbCBvZiBjb3JyZWxhdGlvbiBiZXR3ZWVuIHZhcmlhYmxlcy4gIAoKMi4gRGF0YSBzcGxpdHRpbmcgCgpXZSB1c2VkIHRoZSBgcnNhbXBsZWAgcGFja2FnZSB0byBmaXJzdCBwZXJmb3JtIGFuIGluaXRpYWwgc3BsaXQgb2Ygb3VyIGRhdGEgaW50byB0d28gcGllY2VzOiBhIHRyYWluaW5nIHNldCBhbmQgYSB0ZXN0aW5nIHNldC4gVGhlIHRyYWluaW5nIHNldCB3YXMgdXNlZCB0byBvcHRpbWl6ZSB0aGUgbW9kZWwsIHdoaWxlIHRoZSB0ZXN0aW5nIHNldCB3YXMgdXNlZCBvbmx5IHRvIGV2YWx1YXRlIHRoZSBwZXJmb3JtYW5jZSBvZiBvdXIgZmluYWwgbW9kZWwuIFdlIGFsc28gdXNlZCB0aGUgYHJzYW1wbGVgIHBhY2thZ2UgdG8gY3JlYXRlIGNyb3NzIHZhbGlkYXRpb24gc3Vic2V0cyBvZiBvdXIgdHJhaW5pbmcgZGF0YS4gVGhpcyBhbGxvd2VkIHVzIHRvIGJldHRlciBhc3Nlc3MgdGhlIHBlcmZvcm1hbmNlIG9mIG91ciB0ZXN0ZWQgbW9kZWxzIHVzaW5nIG91ciB0cmFpbmluZyBkYXRhLiAgCgozLiBWYXJpYWJsZSBhc3NpZ25tZW50IGFuZCBwcmUtcHJvY2Vzc2luZyAgIAoKV2UgdXNlZCB0aGUgYHJlY2lwZXNgIHBhY2thZ2UgdG8gYXNzaWduIHZhcmlhYmxlIHJvbGVzIChzdWNoIGFzIG91dGNvbWUsIHByZWRpY3RvciwgYW5kIGlkIHZhcmlhYmxlKS4gV2UgYWxzbyB1c2VkIHRoaXMgcGFja2FnZSB0byBjcmVhdGUgYSByZWNpcGUgZm9yIHByZS1wcm9jZXNzaW5nIG91ciB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhLiBUaGlzIGludm9sdmVkIHN0ZXBzIHN1Y2ggYXM6IGAgc3RlcF9kdW1teWAgdG8gY3JlYXRlIGR1bW15IG51bWVyaWMgZW5jb2RpbmdzIG9mIG91ciBjYXRlZ29yaWNhbCB2YXJpYWJsZXMsIGBzdGVwX2NvcnJgIHRvIHJlbW92ZSBoaWdobHkgY29ycmVsYXRlZCB2YXJpYWJsZXMsIGBzdGVwX256dmAgdG8gcmVtb3ZlIG5lYXIgemVybyB2YXJpYW5jZSB2YXJpYWJsZXMgdGhhdCB3b3VsZCBjb250cmlidXRlIGxpdHRsZSB0byBvdXIgbW9kZWwgYW5kIHBvdGVudGlhbGx5IGFkZCBub2lzZS4gIFdlIGxlYXJuZWQgdGhhdCBvbmNlIG91ciByZWNpcGUgd2FzIGNyZWF0ZWQgYW5kIHByZXBwZWQgdXNpbmcgYHByZXAoKWB3ZSBjb3VsZCBleHRyYWN0IHRoZSBwcmUtcHJvY2Vzc2VkIHRyYWluaW5nIGRhdGEgdXNpbmcgYGp1aWNlKClgIG9yIG91ciBwcmUtcHJvY2Vzc2VkIHRlc3RpbmcgZGF0YSB1c2luZyBgYmFrZSgpYC4gV2UgYWxzbyBsZWFybmVkIHRoYXQgaWYgd2UgdXNlZCB0aGUgbmV3ZXIgd29ya2Zsb3dzIHBhY2thZ2UgdGhhdCB3ZSBkaWQgbm90IG5lZWQgdG8gdGhlIGBwcmVwKClgLCBganVpY2UoKWAsIG9yIGBiYWtlKClgIGZ1bmN0aW9ucywgYnV0IHRoYXQgaXQgaXMgc3RpbGwgdXNlZnVsIHRvIGtub3cgaG93IHRvIGRvIHNvIGlmIHdlIHdhbnQgdG8gbG9vayBhdCBvdXIgZGF0YSBhbmQgaG93IHRoZSByZWNpcGUgaXMgaW5mbHVlbmNpbmcgaXQgbW9yZSBkZWVwbHkuICAKCjQuIE1vZGVsIHNwZWNpZmljYXRpb24sIGZpdHRpbmcsIHR1bmluZyBhbmQgcGVyZm9ybWFuY2UgZXZhbHVhdGlvbiB1c2luZyB0aGUgdHJhaW5pbmcgZGF0YSAgCgpXZSBsZWFybmVkIHRoYXQgdGhlIG1vZGVsIG5lZWRzIHRvIGZpcnN0IGJlIGZpdCB0byB0aGUgdHJhaW5pbmcgZGF0YS4gV2UgbGVhcm5lZCB0aGF0IGluIGJvdGggY2xhc3NpZmljYXRpb24gYW5kIHByZWRpY3Rpb24sIHRoZSBtb2RlbCBpcyBmaXQgdG8gdGhlIHRyYWluaW5nIGRhdGEgYW5kIHRoZSBleHBsYW5hdG9yeSB2YXJpYWJsZXMgYXJlIHVzZWQgdG8gZXN0aW1hdGUgbnVtZXJpYyB2YWx1ZXMgKGluIHRoZSBjYXNlIG9mIHByZWRpY3Rpb24pIG9yIGNhdGVnb3JpY2FsIHZhbHVlcyAoaW4gdGhlIGNhc2Ugb2YgY2xhc3NpZmljYXRpb24pIG9mIHRoZSBvdXRjb21lIHZhcmlhYmxlIG9mIGludGVyZXN0LiBXZSBsZWFybmVkIHRoYXQgd2Ugc3BlY2lmeSB0aGUgbW9kZWwgYW5kIGl0cyBzcGVjaWZpY2F0aW9ucyB1c2luZyB0aGUgYHBhcm5zaXBgIHBhY2thZ2UgYW5kIHRoYXQgd2UgYWxzbyB1c2UgdGhpcyBwYWNrYWdlIHRvIGZpdCB0aGUgbW9kZWwgdXNpbmcgdGhlIGBmaXQoKWAgZnVuY3Rpb24uIFdlIGxlYXJuZWQgdGhhdCB3ZSBpZiBqdXN0IHVzZSBgcGFyc25pcGAgdG8gZml0IHRoZSBtb2RlbCwgdGhlbiB3ZSBuZWVkIHRvIHVzZSB0aGUgcHJlLXByb2Nlc3NlZCB0cmFpbmluZyBkYXRhIChvdXRwdXQgZnJvbSBganVpY2UoKWApLiBXZSBsZWFybmVkIHRoYXQgd2UgY2FuIHVzZSB0aGUgcmF3IHRyYWluaW5nIGRhdGEgaWYgd2UgdXNlIHRoZSBgd29ya2Zsb3dzYCBwYWNrYWdlIHRvIGNyZWF0ZSBhIHdvcmtmbG93IHRoYXQgcHJlLXByb2Nlc3NlcyBvdXIgZGF0YSBmb3IgdXMuICAgCgpXZSBsZWFybmVkIHRoYXQgaWYgdGhlIG1vZGVsIGZpdHMgd2VsbCB0aGFuIHRoZSBlc3RpbWF0ZWQgdmFsdWVzIHdpbGwgYmUgdmVyeSBzaW1pbGFyIHRvIHRoZSB0cnVlIG91dGNvbWUgdmFyaWFibGUgdmFsdWVzIGluIG91ciB0cmFpbmluZyBkYXRhLiBXZSBsZWFybmVkIHRoYXQgd2UgY2FuIGFzc2VzcyBtb2RlbCBwZXJmb3JtYW5jZSB1c2luZyB0aGUgYHlhcmRzdGlja2AgcGFja2FnZSB3aXRoIHRoZSBgbWV0cmljc2AgZnVuY3RpbyBvciB0aGUgYHR1bmVgIHBhY2thZ2UgYW5kIHRoZSBgY29sbGVjdF9tZXRyaWNzKClgIGZ1bmN0aW9uIChyZXF1aXJlZCBpZiB1c2luZyBjcm9zcyB2YWxpZGF0aW9uIG9yIHR1bmluZykuIFdlIGFsc28gbGVhcm5lZCB0aGF0IHdlIGNhbiB1c2Ugc3Vic2V0cyBvZiBvdXIgdHJhaW5pbmcgZGF0YSAod2hpY2ggd2UgY3JlYXRlZCB3aXRoIHRoZSBgcnNhbXBsZWAgcGFja2FnZSkgdG8gcGVyZm9ybSBjcm9zcyB2YWxpZGF0aW9uIHRvIGdldCBhIGJldHRlciBlc3RpbWF0ZSBhYm91dCB0aGUgcGVyZm9ybWFuY2Ugb2Ygb3VyIG1vZGVsIHVzaW5nIG91ciB0cmFpbmluZyBkYXRhLCBhcyB3ZSB3YW50IG91ciByZXN1bHRzIHRvIGJlIGdlbmVyYWxpemFibGUgYW5kIHRvIHBlcmZvcm0gd2VsbCB3aXRoIG90aGVyIGRhdGEsIG5vdCBqdXN0IG91ciB0cmFpbmluZyBkYXRhLiBXZSB1c2VkIHRoZSBgZml0X3Jlc2FtcGxlcygpYCBmdW5jdGlvbiBvZiB0aGUgdHVuZSBwYWNrYWdlIHRvIGZpdCBvdXIgbW9kZWwgb24gb3VyIGRpZmZlcmVudCB0cmFpbmluZyBkYXRhIHN1YnNldHMgYW5kIHRoZSBgY29sbGVjdF9tZXRyaWNzKClgIGZ1bmN0aW9uIChhbHNvIG9mIHRoZSBgdHVuZWAgcGFja2FnZSkgdG8gZXZhbHVhdGUgbW9kZWwgcGVyZm9ybWFuY2UgdXNpbmcgdGhlc2Ugc3Vic2V0cy4gIFdlIGFsc28gbGVhcm5lZCB0aGF0IHdlIGNhbiBwb3RlbnRpYWxseSBpbXByb3ZlIG1vZGVsIHBlcmZvcm1hbmNlIGJ5IHR1bmluZyBhc3BlY3RzIGFib3V0IHRoZSBtb2RlbCBjYWxsZWQgW2h5cGVycGFyYW1ldGVyc10oaHR0cHM6Ly9tYWNoaW5lbGVhcm5pbmdtYXN0ZXJ5LmNvbS9kaWZmZXJlbmNlLWJldHdlZW4tYS1wYXJhbWV0ZXItYW5kLWEtaHlwZXJwYXJhbWV0ZXIvKXt0YXJnZXQ9Il9ibGFuayJ9IHRvIGRldGVybWluZSB0aGUgYmVzdCBvcHRpb24gZm9yIG1vZGVsIHBlcmZvcm1hbmNlLiBXZSBsZWFybmVkIHRoYXQgd2UgY2FuIGRvIHRoaXMgdXNpbmcgdGhlIGB0dW5lYCBhbmQgYGRpYWxzYCBwYWNrYWdlcyBhbmQgZXZhbHVhdGluZyB0aGUgcGVyZm9ybWFuY2Ugb2Ygb3VyIG1vZGVsIHdpdGggdGhlIGRpZmZlcmVudCBoeXBlcnBhcmFtZXRlciBvcHRpb25zIGFuZCBvdXIgdHJhaW5pbmcgZGF0YSBzdWJzZXRzIHRoYXQgd2UgdXNlZCBmb3IgY3Jvc3MgdmFsaWRhdGlvbi4gQWZ0ZXIgd2UgdGVzdGVkIHNldmVyYWwgZGlmZmVyZW50IG1ldGhvZHMgdG8gbW9kZWwgb3VyIGRhdGEsIHdlIGNvbXBhcmVkIHRoZW0gdG8gY2hvb3NlIHRoZSBiZXN0IHBlcmZvcm1pbmcgbW9kZWwgYXMgb3VyIGZpbmFsIG1vZGVsLiAgCgoKNS4gT3ZlcmFsbCBtb2RlbCBwZXJmb3JtYW5jZSBldmFsdWF0aW9uICAKCk9uY2Ugd2UgY2hvc2Ugb3VyIGZpbmFsIG1vZGVsLCB3ZSBldmFsdWF0ZWQgdGhlIGZpbmFsIG1vZGVsIHBlcmZvcm1hbmNlIHVzaW5nIHRoZSB0ZXN0aW5nIGRhdGEgdXNpbmcgdGhlIGBsYXN0X2ZpdCgpYCBmdW5jdGlvbiBvZiB0aGUgYHR1bmVgIHBhY2thZ2UuIFRoaXMgZ2l2ZXMgdXMgYSBiZXR0ZXIgZXN0aW1hdGUgYWJvdXQgaG93IHdlbGwgdGhlIG1vZGVsIHdpbGwgcHJlZGljdCBvciBjbGFzc2lmeSB0aGUgb3V0Y29tZSB2YXJpYWJsZSBvZiBpbnRlcmVzdCB3aXRoIG5ldyBpbmRlcGVuZGVudCBkYXRhLiBJZGVhbGx5IG9uZSB3b3VsZCBhbHNvIHBlcmZvcm0gYW4gZXZhbHVhdGlvbiB3aXRoIGluZGVwZW5kZW50IGRhdGEgdG8gcHJvdmlkZSBhIHNlbnNlIG9mIGhvdyBnZW5lcmFsaXphYmxlIHRoZSBtb2RlbCBpcyB0byBvdGhlciBkYXRhIHNvdXJjZXMuIAoKV2UgYWxzbyBzYXcgdGhhdCB3ZSBjYW4gdXNlIHRoZSBgY29sbGVjdF9wcmVkaWN0aW9ucygpYCBmdW5jdGlvbiBvZiB0aGUgYHR1bmVgIHBhY2thZ2UgdG8gZ2V0IHRoZSBwcmVkaWN0aW9ucyBmb3Igb3VyIHRlc3QgZGF0YS4gV2Ugc2F3IHRoYXQgd2UgY2FuIGdldCBtb3JlIGRldGFpbGVkIHByZWRpY3Rpb24gZGF0YSB1c2luZyB0aGUgYHByZWRpY3QoKWAgZnVuY3Rpb24gb2YgdGhlIGBwYXJzbmlwYCBwYWNrYWdlLgoKPC9kZXRhaWxzPgoKCgojIyBTdWdnZXN0ZWQgSG9tZXdvcmsKClN0dWRlbnRzIGNhbiBwcmVkaWN0IGFpciBwb2xsdXRpb24gbW9uaXRvciB2YWx1ZXMgdXNpbmcgYSBkaWZmZXJlbnQgYWxnb3JpdGhtIGFuZCBwcm92aWRlIGFuIGV4cGxhbmF0aW9uIGZvciBob3cgdGhhdCBhbGdvcml0aG0gd29ya3MgYW5kIHdoeSBpdCBtYXkgYmUgYSBnb29kIGNob2ljZSBmb3IgbW9kZWxpbmcgdGhpcyBkYXRhLgoKCiMgKipBZGRpdGlvbmFsIEluZm9ybWF0aW9uKioKKioqCgojIyBIZWxwZnVsIExpbmtzCgoxLiBBIHJldmlldyBvZiBbdGlkeW1vZGVsc10oaHR0cHM6Ly9ydmlld3MucnN0dWRpby5jb20vMjAxOS8wNi8xOS9hLWdlbnRsZS1pbnRyby10by10aWR5bW9kZWxzLyl7dGFyZ2V0PSJfYmxhbmsifSAgCjIuIEEgW2NvdXJzZSBvbiB0aWR5bW9kZWxzXShodHRwczovL2p1bGlhc2lsZ2UuY29tL2Jsb2cvdGlkeW1vZGVscy1tbC1jb3Vyc2UvKXt0YXJnZXQ9Il9ibGFuayJ9IGJ5IEp1bGlhIFNpbGdlICAKMy4gW01vcmUgZXhhbXBsZXMsIGV4cGxhbmF0aW9ucywgYW5kIGluZm8gYWJvdXQgdGlkeW1vZGVscyBkZXZlbG9wbWVudF0oaHR0cHM6Ly93d3cudGlkeW1vZGVscy5vcmcvbGVhcm4vKXt0YXJnZXQ9Il9ibGFuayJ9IGZyb20gdGhlIGRldmVsb3BlcnMgIAo0LiBBIGd1aWRlIGZvciBbcHJlLXByb2Nlc3Npbmcgd2l0aCByZWNpcGVzXShodHRwOi8vd3d3LnJlYmVjY2FiYXJ0ZXIuY29tL2Jsb2cvMjAxOS0wNi0wNl9wcmVfcHJvY2Vzc2luZy8pe3RhcmdldD0iX2JsYW5rIn0gIAo1LiBBIFtndWlkZV0oaHR0cHM6Ly9icmlhdHRlLmdpdGh1Yi5pby9nZ2NvcnIvKXt0YXJnZXQ9Il9ibGFuayJ9IGZvciB1c2luZyBHR2FsbHkgdG8gY3JlYXRlIGNvcnJlbGF0aW9uIHBsb3RzICAKNi4gQSBbZ3VpZGVdKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvYmxvZy8yMDE4LzExL3BhcnNuaXAtMC0wLTEvKXt0YXJnZXQ9Il9ibGFuayJ9IGZvciB1c2luZyBwYXJzbmlwIHRvIHRyeSBkaWZmZXJlbnQgYWxnb3JpdGhtcyBvciBlbmdpbmVzICAKNy4gQSBbbGlzdCBvZiByZWNpcGUgZnVuY3Rpb25zXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3JlY2lwZXMvcmVmZXJlbmNlL2luZGV4Lmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gIAo4LiBBIGdyZWF0IGJsb2cgcG9zdCBhYm91dCBbY3Jvc3MgdmFsaWRhdGlvbl0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL3RyYWluLXRlc3Qtc3BsaXQtYW5kLWNyb3NzLXZhbGlkYXRpb24taW4tcHl0aG9uLTgwYjYxYmVjYTRiNil7dGFyZ2V0PSJfYmxhbmsifSAgCjkuIEEgZGlzY3Vzc2lvbiBhYm91dCBbZXZhbHVhdGluZyBtb2RlbCBwZXJmb3JtYW5jZV0oaHR0cHM6Ly9tZWRpdW0uY29tL0BsaW1hdmFsbGFudGluL21ldHJpY3MtdG8tbWVhc3VyZS1tYWNoaW5lLWxlYXJuaW5nLW1vZGVsLXBlcmZvcm1hbmNlLWU4Yzk2MzY2NTQ3Nil7dGFyZ2V0PSJfYmxhbmsifSBmb3IgYSBkZWVwZXIgZXhwbGFuYXRpb24gYWJvdXQgaG93IHRvIGV2YWx1YXRlIG1vZGVsIHBlcmZvcm1hbmNlICAKMTAuIFtSU3R1ZGlvIGNoZWF0c2hlZXRzXShodHRwczovL3JzdHVkaW8uY29tL3Jlc291cmNlcy9jaGVhdHNoZWV0cy8pe3RhcmdldD0iX2JsYW5rIn0KMTEuIEFuIFtleHBsYW5hdGlvbl0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL3N1cGVydmlzZWQtdnMtdW5zdXBlcnZpc2VkLWxlYXJuaW5nLTE0ZjY4ZTMyZWE4ZCl7dGFyZ2V0PSJfYmxhbmsifSBvZiBzdXBlcnZpc2VkIHZzIHVuc3VwZXJ2aXNlZCBtYWNoaW5lIGxlYXJuaW5nIGFuZCBiaWFzLXZhcmlhbmNlIHRyYWRlLW9mZi4KMTIuIEEgdGhvcm91Z2ggW2V4cGxhbmF0aW9uXShodHRwczovL3JveWFsc29jaWV0eXB1Ymxpc2hpbmcub3JnL2RvaS8xMC4xMDk4L3JzdGEuMjAxNS4wMjAyIzp+OnRleHQ9UHJpbmNpcGFsJTIwY29tcG9uZW50JTIwYW5hbHlzaXMlMjAoUENBKSUyMGlzLHZhcmlhYmxlcyUyMHRoYXQlMjBzdWNjZXNzaXZlbHklMjBtYXhpbWl6ZSUyMHZhcmlhbmNlLil7dGFyZ2V0PSJfYmxhbmsifSBvZiBwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzLgoxMy4gSWYgeW91IGhhdmUgYWNjZXNzLCB0aGlzIGlzIGEgZ3JlYXQgW2Rpc2N1c3Npb25dKGh0dHBzOi8vd3d3LnRhbmRmb25saW5lLmNvbS9kb2kvYWJzLzEwLjEwODAvMDAwMzEzMDUuMTk4NC4xMDQ4MzE4Myl7dGFyZ2V0PSJfYmxhbmsifSAgYWJvdXQgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBpbmRlcGVuZGVuY2UsIG9ydGhvZ29uYWxpdHksIGFuZCBsYWNrIG9mIGNvcnJlbGF0aW9uLgoxNC4gR3JlYXQgW3ZpZGVvIGV4cGxhbmF0aW9uXShodHRwczovL3lvdXR1LmJlL19VVkhuZUJVQlcwKXt0YXJnZXQ9Il9ibGFuayJ9IG9mIFBDQS4gIAoKPHU+VGVybXMgYW5kIGNvbmNlcHRzIGNvdmVyZWQ6PC91PiAgCgpbVGlkeXZlcnNlXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSAgCltJbXB1dGF0aW9uXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9JbXB1dGF0aW9uXyhzdGF0aXN0aWNzKSl7dGFyZ2V0PSJfYmxhbmsifSAgCltUcmFuc2Zvcm1hdGlvbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRGF0YV90cmFuc2Zvcm1hdGlvbl8oc3RhdGlzdGljcykpe3RhcmdldD0iX2JsYW5rIn0gIApbRGlzY3JldGl6YXRpb25dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0Rpc2NyZXRpemF0aW9uX29mX2NvbnRpbnVvdXNfZmVhdHVyZXMpe3RhcmdldD0iX2JsYW5rIn0gIApbRHVtbXkgVmFyaWFibGVzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9EdW1teV92YXJpYWJsZV8oc3RhdGlzdGljcykpe3RhcmdldD0iX2JsYW5rIn0gIApbT25lIEhvdCBFbmNvZGluZ10oaHR0cHM6Ly9tYWNoaW5lbGVhcm5pbmdtYXN0ZXJ5LmNvbS93aHktb25lLWhvdC1lbmNvZGUtZGF0YS1pbi1tYWNoaW5lLWxlYXJuaW5nLyl7dGFyZ2V0PSJfYmxhbmsifSAgCltEYXRhIFR5cGUgQ29udmVyc2lvbnNdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9oYWJsYXIvdmlnbmV0dGVzL2NvbnZlcnQuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSAgCltJbnRlcmFjdGlvbl0oaHR0cHM6Ly9zdGF0aXN0aWNzYnlqaW0uY29tL3JlZ3Jlc3Npb24vaW50ZXJhY3Rpb24tZWZmZWN0cy8pe3RhcmdldD0iX2JsYW5rIn0gIApbTm9ybWFsaXphdGlvbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTm9ybWFsaXphdGlvbl8oc3RhdGlzdGljcykpe3RhcmdldD0iX2JsYW5rIn0gIApbRGltZW5zaW9uYWxpdHkgUmVkdWN0aW9uL1NpZ25hbCBFeHRyYWN0aW9uXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9EaW1lbnNpb25hbGl0eV9yZWR1Y3Rpb24pe3RhcmdldD0iX2JsYW5rIn0gIApbUm93IE9wZXJhdGlvbnNdKGh0dHBzOi8vdGFydGFydXMub3JnL2dhcmV0aC9tYXRocy9MaW5lYXJfQWxnZWJyYS9yb3dfb3BlcmF0aW9ucy5wZGYpe3RhcmdldD0iX2JsYW5rIn0gIApbTmVhciBaZXJvIFZhcmFpbmNlXShodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS9uZWFyLXplcm8tdmFyaWFuY2UtcHJlZGljdG9ycy1zaG91bGQtd2UtcmVtb3ZlLXRoZW0vKXt0YXJnZXQ9Il9ibGFuayJ9ICAKW1BhcmFtZXRlcnMgYW5kIEh5cGVycGFyYW1ldGVyc10oaHR0cHM6Ly9tYWNoaW5lbGVhcm5pbmdtYXN0ZXJ5LmNvbS9kaWZmZXJlbmNlLWJldHdlZW4tYS1wYXJhbWV0ZXItYW5kLWEtaHlwZXJwYXJhbWV0ZXIvKXt0YXJnZXQ9Il9ibGFuayJ9ICAgCltTdXBlcnZpc2VkIGFuZCBVbnNwZXJ2aXNlZCBMZWFybmluZ10oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL3N1cGVydmlzZWQtdnMtdW5zdXBlcnZpc2VkLWxlYXJuaW5nLTE0ZjY4ZTMyZWE4ZCl7dGFyZ2V0PSJfYmxhbmsifSAgCltQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzXShodHRwczovL21lZGl1bS5jb20vQHNhdmFzdGFtaXJrby9wY2EtYS1saW5lYXItdHJhbnNmb3JtYXRpb24tZjhhYWNkNGViMDA3KXt0YXJnZXQ9Il9ibGFuayJ9ICAKW0xpbmVhciBDb21iaW5hdGlvbnNdKGh0dHBzOi8vd3d3Lm1hdGhib290Y2FtcHMuY29tL2xpbmVhci1jb21iaW5hdGlvbnMtdmVjdG9ycy8pe3RhcmdldD0iX2JsYW5rIn0gIApbRGVjaXNpb24gVHJlZV0oaHR0cHM6Ly9tZWRpdW0uY29tL2dyZXlhdG9tL2RlY2lzaW9uLXRyZWVzLWEtc2ltcGxlLXdheS10by12aXN1YWxpemUtYS1kZWNpc2lvbi1kYzUwNmE0MDNhZWIpe3RhcmdldD0iX2JsYW5rIn0gIApbUmFuZG9tIEZvcmVzdF0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL2RlY2lzaW9uLXRyZWUtZW5zZW1ibGVzLWJhZ2dpbmctYW5kLWJvb3N0aW5nLTI2NmE4YmE2MGZkOSl7dGFyZ2V0PSJfYmxhbmsifSAgCgoKPHU+UGFja2FnZXMgdXNlZCBpbiB0aGlzIGNhc2Ugc3R1ZHk6IDwvdT4KCiBQYWNrYWdlICAgfCBVc2UgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCi0tLS0tLS0tLS0gfC0tLS0tLS0tLS0tLS0KW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9qZW5ueWJjL2hlcmVfaGVyZSl7dGFyZ2V0PSJfYmxhbmsifSAgICAgICB8IHRvIGVhc2lseSBsb2FkIGFuZCBzYXZlIGRhdGEKW3JlYWRyXShodHRwczovL3JlYWRyLnRpZHl2ZXJzZS5vcmcvKXt0YXJnZXQ9Il9ibGFuayJ9ICAgICAgfCB0byBpbXBvcnQgdGhlIENTViBmaWxlIGRhdGEKW2RwbHlyXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvKXt0YXJnZXQ9Il9ibGFuayJ9ICAgICAgfCB0byB2aWV3L2FycmFuZ2UvZmlsdGVyL3NlbGVjdC9jb21wYXJlIHNwZWNpZmljIHN1YnNldHMgb2YgdGhlIGRhdGEgCltza2ltcl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3NraW1yL2luZGV4Lmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gICAgICB8IHRvIGdldCBhbiBvdmVydmlldyBvZiBkYXRhCltzdW1tYXJ5dG9vbHNdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9za2ltci9pbmRleC5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9ICAgICAgfCB0byBnZXQgYW4gb3ZlcnZpZXcgb2YgZGF0YSBpbiBhIGRpZmZlcmVudCBzdHlsZQpbbWFncml0dHJdKGh0dHBzOi8vbWFncml0dHIudGlkeXZlcnNlLm9yZy9hcnRpY2xlcy9tYWdyaXR0ci5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9ICAgfCB0byB1c2UgdGhlIGAlPD4lYCBwaXBwaW5nIG9wZXJhdG9yIApbY29ycnBsb3RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9jb3JycGxvdC92aWduZXR0ZXMvY29ycnBsb3QtaW50cm8uaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSB8IHRvIG1ha2UgbGFyZ2UgY29ycmVsYXRpb24gcGxvdHMKW0dHYWxseV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL0dHYWxseS9HR2FsbHkucGRmKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gbWFrZSBzbWFsbGVyIGNvcnJlbGF0aW9uIHBsb3RzICAKW3JzYW1wbGVdKGh0dHBzOi8vdGlkeW1vZGVscy5naXRodWIuaW8vcnNhbXBsZS9hcnRpY2xlcy9CYXNpY3MuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSAgIHwgdG8gc3BsaXQgdGhlIGRhdGEgaW50byB0ZXN0aW5nIGFuZCB0cmFpbmluZyBzZXRzIGFuZCB0byBzcGxpdCB0aGUgdHJhaW5pbmcgc2V0IGZvciBjcm9zcy12YWxpZGF0aW9uICAKW3JlY2lwZXNdKGh0dHBzOi8vdGlkeW1vZGVscy5naXRodWIuaW8vcmVjaXBlcy8pe3RhcmdldD0iX2JsYW5rIn0gICB8IHRvIHByZS1wcm9jZXNzIGRhdGEgZm9yIG1vZGVsaW5nIGluIGEgdGlkeSBhbmQgcmVwcm9kdWNpYmxlIHdheSBhbmQgdG8gZXh0cmFjdCBwcmUtcHJvY2Vzc2VkIGRhdGEgKG1ham9yIGZ1bmN0aW9ucyBhcmUgYHJlY2lwZSgpYCAsIGBwcmVwKClgIGFuZCB2YXJpb3VzIHRyYW5zZm9ybWF0aW9uIGBzdGVwXyooKWAgZnVuY3Rpb25zLCBhcyB3ZWxsIGFzIGBqdWljZSgpYCAtIGV4dHJhY3RzIGZpbmFsIHByZS1wcm9jZXNzZWQgdHJhaW5pbmcgZGF0YSBhbmQgYGJha2UoKWAgLSBhcHBsaWVzIHJlY2lwZSBzdGVwcyB0byB0ZXN0aW5nIGRhdGEpLiBTZWUgW2hlcmVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9yZWNpcGVzL3JlY2lwZXMucGRmKXt0YXJnZXQ9Il9ibGFuayJ9ICBmb3IgbW9yZSBpbmZvLgpbcGFyc25pcF0oaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby9wYXJzbmlwLyl7dGFyZ2V0PSJfYmxhbmsifSAgIHwgYW4gaW50ZXJmYWNlIHRvIGNyZWF0ZSBtb2RlbHMgKG1ham9yIGZ1bmN0aW9ucyBhcmUgIGBmaXQoKWAsIGBzZXRfZW5naW5lKClgKQpbeWFyZHN0aWNrXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3lhcmRzdGljay8pe3RhcmdldD0iX2JsYW5rIn0gICB8IHRvIGV2YWx1YXRlIHRoZSBwZXJmb3JtYW5jZSBvZiBtb2RlbHMKW2Jyb29tXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnL2Jsb2cvMjAxOC8wNy9icm9vbS0wLTUtMC8pe3RhcmdldD0iX2JsYW5rIn0gfCB0byBnZXQgdGlkeSBvdXRwdXQgZm9yIG91ciBtb2RlbCBmaXQgYW5kIHBlcmZvcm1hbmNlCltnZ3Bsb3QyXShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy8pe3RhcmdldD0iX2JsYW5rIn0gICAgfCB0byBtYWtlIHZpc3VhbGl6YXRpb25zIHdpdGggbXVsdGlwbGUgbGF5ZXJzCltkaWFsc10oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy9ibG9nLzIwMTkvMTAvZGlhbHMtMC0wLTMvKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gc3BlY2lmeSBoeXBlci1wYXJhbWV0ZXIgdHVuaW5nClt0dW5lXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gcGVyZm9ybSBjcm9zcyB2YWxpZGF0aW9uLCB0dW5lIGh5cGVyLXBhcmFtZXRlcnMsIGFuZCBnZXQgcGVyZm9ybWFuY2UgbWV0cmljcwpbd29ya2Zsb3dzXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvd29ya2Zsb3dzL3ZlcnNpb25zLzAuMS4xKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gY3JlYXRlIG1vZGVsaW5nIHdvcmtmbG93IHRvIHN0cmVhbWxpbmUgdGhlIG1vZGVsaW5nIHByb2Nlc3MKW3ZpcF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3ZpcC92aXAucGRmKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gY3JlYXRlIHZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdHMKW3JhbmRvbUZvcmVzdF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3JhbmRvbUZvcmVzdC9yYW5kb21Gb3Jlc3QucGRmKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gcGVyZm9ybSB0aGUgcmFuZG9tIGZvcmVzdCBhbmFseXNpcwpbZG9QYXJhbGxlbF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2RvUGFyYWxsZWwvZG9QYXJhbGxlbC5wZGYpIHwgdG8gZml0IGNyb3NzIHZhbGlkYXRpb24gc2FtcGxlcyBpbiBwYXJhbGxlbCAKW3N0cmluZ3JdKGh0dHBzOi8vc3RyaW5nci50aWR5dmVyc2Uub3JnL2FydGljbGVzL3N0cmluZ3IuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSAgICB8IHRvIG1hbmlwdWxhdGUgdGhlIHRleHQgdGhlIG1hcCBkYXRhClt0aWR5cl0oaHR0cHM6Ly90aWR5ci50aWR5dmVyc2Uub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSAgICAgIHwgdG8gc2VwYXJhdGUgZGF0YSB3aXRoaW4gYSBjb2x1bW4gaW50byBtdWx0aXBsZSBjb2x1bW5zCltybmF0dXJhbGVhcnRoXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcm5hdHVyYWxlYXJ0aC9SRUFETUUuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSB8IHRvIGdldCB0aGUgZ2VvbWV0cnkgZGF0YSBmb3IgdGhlIGVhcnRoIHRvIHBsb3QgdGhlIFVTClttYXBzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvbWFwcy9tYXBzLnBkZil7dGFyZ2V0PSJfYmxhbmsifSB8IHRvIGdldCBtYXAgZGF0YWJhc2UgZGF0YSBhYm91dCBjb3VudGllcyB0byBkcmF3IHRoZW0gb24gb3VyIFVTIG1hcApbc2ZdKGh0dHBzOi8vci1zcGF0aWFsLmdpdGh1Yi5pby9zZi8pe3RhcmdldD0iX2JsYW5rIn0gIHwgdG8gY29udmVydCB0aGUgbWFwIGRhdGEgaW50byBhIGRhdGEgZnJhbWUKW2x3Z2VvbV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2x3Z2VvbS9sd2dlb20ucGRmKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gdXNlIHRoZSBgc2ZgIGZ1bmN0aW9uIHRvIGNvbnZlcnQgdGhlIG1hcCBnZW9ncmFwaGljYWwgZGF0YQpbcmdlb3NdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9yZ2Vvcy9yZ2Vvcy5wZGYpe3RhcmdldD0iX2JsYW5rIn0gfCB0byB1c2UgZ2VvbWV0cnkgZGF0YQpbcGF0Y2h3b3JrXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcGF0Y2h3b3JrL3BhdGNod29yay5wZGYpe3RhcmdldD0iX2JsYW5rIn0gfCB0byBhbGxvdyBwbG90cyB0byBiZSBjb21iaW5lZAoKCiMjIFNlc3Npb24gaW5mbyAKCgpgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGAKCgojIyBBY2tub3dsZWRnZW1lbnRzCgoKV2Ugd291bGQgbGlrZSB0byBhY2tub3dsZWRnZSBbUm9nZXIgUGVuZ10oaHR0cDovL3d3dy5iaW9zdGF0Lmpoc3BoLmVkdS9+cnBlbmcvKSwgW01lZ2FuIExhdHNoYXddKGh0dHBzOi8vd3d3Lmpoc3BoLmVkdS9mYWN1bHR5L2RpcmVjdG9yeS9wcm9maWxlLzE3MDgvbWVnYW4td2VpbC1sYXRzaGF3KSwgYW5kIFtLcmlzdGVuIEtvZWhsZXJdKGh0dHBzOi8vd3d3Lmpoc3BoLmVkdS9mYWN1bHR5L2RpcmVjdG9yeS9wcm9maWxlLzI5Mjgva2lyc3Rlbi1rb2VobGVyKWZvciBhc3Npc3RpbmcgaW4gZnJhbWluZyB0aGUgbWFqb3IgZGlyZWN0aW9uIG9mIHRoZSBjYXNlIHN0dWR5LgoKV2Ugd291bGQgYWxzbyBsaWtlIHRvIGFja25vd2xlZGdlIHRoZSBbQmxvb21iZXJnIEFtZXJpY2FuIEhlYWx0aCBJbml0aWF0aXZlXShodHRwczovL2FtZXJpY2FuaGVhbHRoLmpodS5lZHUvKSBmb3IgZnVuZGluZyB0aGlzIHdvcmsuIAo=